config: Exclude DataSourceAutoConfiguration and update tenant database connection URL in tenants.json.
Some checks failed
Build and Push Docker Images / build-and-push-backend (push) Successful in 29s
Build and Push Docker Images / build-and-push-frontend (push) Successful in 11s
Build and Push Docker Images / deploy-to-k8s (push) Has been cancelled

This commit is contained in:
Zuev
2026-03-12 22:31:09 +03:00
parent 14cc006f06
commit 3579ef9f1c
4 changed files with 56 additions and 8 deletions

View File

@@ -2,8 +2,9 @@ package com.magistr.app;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application { public class Application {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -5,16 +5,21 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import jakarta.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.List;
/** /**
* Конфигурация мультитенантного DataSource. * Конфигурация мультитенантного DataSource.
@@ -38,6 +43,7 @@ public class TenantDataSourceConfig implements WebMvcConfigurer {
private String defaultDbPassword; private String defaultDbPassword;
@Bean @Bean
@Primary
public TenantRoutingDataSource tenantRoutingDataSource() { public TenantRoutingDataSource tenantRoutingDataSource() {
TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource(); TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
@@ -45,7 +51,7 @@ public class TenantDataSourceConfig implements WebMvcConfigurer {
List<TenantConfig> tenants = loadTenantsFromFile(); List<TenantConfig> tenants = loadTenantsFromFile();
// Если нет тенантов и есть дефолтный datasource — создаём "default" тенант // Если нет тенантов и есть дефолтный datasource — создаём "default" тенант
if (tenants.isEmpty() && !defaultDbUrl.isBlank()) { if (tenants.isEmpty() && defaultDbUrl != null && !defaultDbUrl.isBlank()) {
TenantConfig defaultTenant = new TenantConfig( TenantConfig defaultTenant = new TenantConfig(
"Default", "default", defaultDbUrl, defaultDbUsername, defaultDbPassword "Default", "default", defaultDbUrl, defaultDbUsername, defaultDbPassword
); );
@@ -62,18 +68,44 @@ public class TenantDataSourceConfig implements WebMvcConfigurer {
} }
} }
if (tenants.isEmpty()) { if (routingDataSource.getTenantConfigs().isEmpty()) {
log.warn("No tenants configured! Backend will fail on DB queries."); log.warn("=== НЕТ НАСТРОЕННЫХ ТЕНАНТОВ ===");
log.warn("Backend запустится, но API не будет работать без подключения к БД.");
log.warn("Добавьте тенант через POST /api/database/tenants или настройте tenants.json");
} }
return routingDataSource; return routingDataSource;
} }
@Bean @Bean
@Primary
public DataSource dataSource(TenantRoutingDataSource tenantRoutingDataSource) { public DataSource dataSource(TenantRoutingDataSource tenantRoutingDataSource) {
return tenantRoutingDataSource; return tenantRoutingDataSource;
} }
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource, EntityManagerFactoryBuilder builder) {
Map<String, String> jpaProps = new HashMap<>();
jpaProps.put("hibernate.hbm2ddl.auto", "update");
jpaProps.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
return builder
.dataSource(dataSource)
.packages("com.magistr.app.model")
.persistenceUnit("default")
.properties(jpaProps)
.build();
}
@Bean
@Primary
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean @Bean
public TenantInterceptor tenantInterceptor() { public TenantInterceptor tenantInterceptor() {
return new TenantInterceptor(); return new TenantInterceptor();

View File

@@ -8,6 +8,7 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -21,6 +22,13 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource {
private final Map<String, TenantConfig> tenantConfigs = new ConcurrentHashMap<>(); private final Map<String, TenantConfig> tenantConfigs = new ConcurrentHashMap<>();
private final Map<Object, Object> dataSources = new ConcurrentHashMap<>(); private final Map<Object, Object> dataSources = new ConcurrentHashMap<>();
private boolean initialized = false;
public TenantRoutingDataSource() {
// Устанавливаем пустой map чтобы afterPropertiesSet не падал
setTargetDataSources(new HashMap<>());
setLenientFallback(false);
}
@Override @Override
protected Object determineCurrentLookupKey() { protected Object determineCurrentLookupKey() {
@@ -41,6 +49,7 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource {
// Обновляем target data sources // Обновляем target data sources
setTargetDataSources(dataSources); setTargetDataSources(dataSources);
afterPropertiesSet(); afterPropertiesSet();
initialized = true;
log.info("Added tenant '{}' -> {}", domain, config.getUrl()); log.info("Added tenant '{}' -> {}", domain, config.getUrl());
} }
@@ -108,6 +117,10 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource {
return tenantConfigs.containsKey(domain.toLowerCase()); return tenantConfigs.containsKey(domain.toLowerCase());
} }
public boolean isInitialized() {
return initialized && !dataSources.isEmpty();
}
private HikariDataSource createDataSource(TenantConfig config) { private HikariDataSource createDataSource(TenantConfig config) {
HikariDataSource ds = new HikariDataSource(); HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(config.getUrl()); ds.setJdbcUrl(config.getUrl());
@@ -119,6 +132,8 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource {
ds.setConnectionTimeout(10000); ds.setConnectionTimeout(10000);
ds.setIdleTimeout(300000); ds.setIdleTimeout(300000);
ds.setMaxLifetime(600000); ds.setMaxLifetime(600000);
// Не падать при инициализации если БД недоступна
ds.setInitializationFailTimeout(-1);
return ds; return ds;
} }
} }

View File

@@ -2,7 +2,7 @@
{ {
"name": "Default (dev)", "name": "Default (dev)",
"domain": "default", "domain": "default",
"url": "jdbc:postgresql://db:5432/app_db", "url": "jdbc:postgresql://192.168.1.87:5432/app_db",
"username": "myuser", "username": "myuser",
"password": "supersecretpassword" "password": "supersecretpassword"
} }