/
Al
/
TESamples
Обзор
Документация
Войти
/
Al
/
TESamples
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
apps/spring/bff-agent/src/main/java/com/tokenexchange/bff/IdpRouter.java
106 строк
5 KB
Al
Initial Commit
11 май 2026, 17:40
11 май 2026, 17:40
0a04577
Код
Авторство
О чём код?
package com.tokenexchange.bff; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.client.ResourceAccessException; import java.util.function.Function; /** * Простой Primary/Secondary IdP-роутер для Phase 5 / FR-06.5. * * <p>Стратегия — active/passive: первая попытка идёт на primary; при * connect/IO-ошибке (ResourceAccessException) — повторяем на secondary. * Метрика {@code idp_failover_events_total{from,to}} инкрементируется при каждом * fallback'е (FR-09.2). * * <p>Что отложено в подфазы: * - circuit breaker (FR-06.1) — Resilience4j; * - sticky-on-failure cooldown (FR-06.5) — bool-state + timer; * - probe-based return (FR-06.5) — отдельный шедулер. */ @Component public class IdpRouter { public record IdpEndpoint( String name, // "primary" | "secondary" String baseUrl, // http://toxiproxy:18080 | http://keycloak:8080 String realm, // realm-primary | realm-secondary String bffClientSecret, String agentClientSecret ) { public String tokenUrl() { return baseUrl + "/realms/" + realm + "/protocol/openid-connect/token"; } } private static final Logger log = LoggerFactory.getLogger(IdpRouter.class); private final IdpEndpoint primary; private final IdpEndpoint secondary; private final MeterRegistry meterRegistry; private final boolean failoverEnabled; public IdpRouter( @Value("${tokenexchange.kc.primary.base-url:}") String primaryBaseUrl, @Value("${tokenexchange.kc.secondary.base-url:}") String secondaryBaseUrl, @Value("${tokenexchange.kc.primary.realm:realm-primary}") String primaryRealm, @Value("${tokenexchange.kc.secondary.realm:realm-secondary}") String secondaryRealm, @Value("${tokenexchange.kc.primary.bff-secret:}") String primaryBffSecret, @Value("${tokenexchange.kc.secondary.bff-secret:}") String secondaryBffSecret, @Value("${tokenexchange.kc.primary.agent-secret:}") String primaryAgentSecret, @Value("${tokenexchange.kc.secondary.agent-secret:}") String secondaryAgentSecret, // Fallback на старые однопрефиксные настройки (Phase 2 без failover): @Value("${tokenexchange.kc.base-url:http://keycloak:8080}") String legacyBaseUrl, @Value("${tokenexchange.kc.realm:realm-primary}") String legacyRealm, @Value("${tokenexchange.bff.client-secret:bff-agent-dev-secret}") String legacyBffSecret, @Value("${tokenexchange.agent.client-secret:agent-runtime-dev-secret}") String legacyAgentSecret, MeterRegistry meterRegistry ) { this.meterRegistry = meterRegistry; if (!primaryBaseUrl.isBlank() && !secondaryBaseUrl.isBlank()) { this.primary = new IdpEndpoint("primary", primaryBaseUrl, primaryRealm, primaryBffSecret, primaryAgentSecret); this.secondary = new IdpEndpoint("secondary", secondaryBaseUrl, secondaryRealm, secondaryBffSecret, secondaryAgentSecret); this.failoverEnabled = true; log.info("IdpRouter: failover ON (primary={} secondary={})", primary.tokenUrl(), secondary.tokenUrl()); } else { this.primary = new IdpEndpoint("primary", legacyBaseUrl, legacyRealm, legacyBffSecret, legacyAgentSecret); this.secondary = null; this.failoverEnabled = false; log.info("IdpRouter: failover OFF (single endpoint {})", primary.tokenUrl()); } } public IdpEndpoint primary() { return primary; } /** * Выполняет операцию на primary. При ResourceAccessException (connect timeout/refused) * и включённом failover — повторяет на secondary, инкрементирует метрику. */ public <T> T execute(Function<IdpEndpoint, T> op) { if (!failoverEnabled || secondary == null) return op.apply(primary); try { return op.apply(primary); } catch (ResourceAccessException e) { log.warn("IdP primary unreachable, failing over to secondary: {}", e.getMessage()); recordFailover(primary.name(), secondary.name()); return op.apply(secondary); } } private void recordFailover(String from, String to) { if (meterRegistry == null) return; Counter.builder("idp.failover.events") .description("IdP failover events") .tag("from", from).tag("to", to) .register(meterRegistry).increment(); } }