From 512aa82d64bc10e0eb055de831768bdafc61d465 Mon Sep 17 00:00:00 2001 From: "adel.kalimullin" Date: Mon, 12 May 2025 14:47:38 +0300 Subject: [PATCH 1/3] SUPPORT-9165: add active session execute and refactor --- backend/src/main/java/AppConfig.java | 3 +- ...dition.java => KafkaEnabledCondition.java} | 6 +- .../dao/ActiveSessionDao.java | 43 ++++ .../dao/IdmDirectoriesDao.java | 5 +- .../kafka/KafkaConfig.java | 27 +- .../kafka/KafkaConsumerInitializer.java | 4 +- .../listener/IdmDirectoriesListener.java | 10 +- .../listener/SessionResponseListener.java | 42 ++++ .../model/{ => idm}/AccountData.java | 5 +- .../model/{ => idm}/DomainData.java | 2 +- .../model/{ => idm}/RoleData.java | 2 +- .../ReferenceEntityDeserializer.java | 2 +- .../model/sso/SessionStateResponse.java | 46 ++++ .../model/sso/UserAccount.java | 33 +++ .../model/sso/UserIdentity.java | 19 ++ .../model/sso/UserSessionInfo.java | 37 +++ .../service/IdmDirectoriesDaoService.java | 4 +- .../service/IdmDirectoriesService.java | 10 +- .../service/SessionResponseHolder.java | 74 ++++++ .../service/SessionService.java | 69 ++++++ .../processor/impl/AccountDataProcessor.java | 6 +- .../processor/impl/DomainDataProcessor.java | 6 +- .../processor/impl/RoleDataProcessor.java | 6 +- .../scheduler/SessionScheduledService.java | 38 +++ .../java/exception/JsonParseException.java | 11 + .../db_beans/DefaultCatalog.java | 7 + .../business_metrics/db_beans/auth/Auth.java | 55 +++++ .../business_metrics/db_beans/auth/Keys.java | 28 +++ .../db_beans/auth/Tables.java | 20 ++ .../db_beans/auth/tables/ActiveSession.java | 233 ++++++++++++++++++ .../tables/records/ActiveSessionRecord.java | 109 ++++++++ ...0250512-SUPPORT-9165-add_session_table.xml | 47 ++++ .../resources/config/v_1.0/changelog-1.0.xml | 1 + config/micord.env | 9 +- .../main/resources/database/datasource.xml | 1 + 35 files changed, 978 insertions(+), 42 deletions(-) rename backend/src/main/java/ervu_business_metrics/config/{IdmReconcileEnabledCondition.java => KafkaEnabledCondition.java} (66%) create mode 100644 backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java create mode 100644 backend/src/main/java/ervu_business_metrics/kafka/listener/SessionResponseListener.java rename backend/src/main/java/ervu_business_metrics/model/{ => idm}/AccountData.java (93%) rename backend/src/main/java/ervu_business_metrics/model/{ => idm}/DomainData.java (99%) rename backend/src/main/java/ervu_business_metrics/model/{ => idm}/RoleData.java (97%) rename backend/src/main/java/ervu_business_metrics/model/{ => idm}/deserializer/ReferenceEntityDeserializer.java (94%) create mode 100644 backend/src/main/java/ervu_business_metrics/model/sso/SessionStateResponse.java create mode 100644 backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java create mode 100644 backend/src/main/java/ervu_business_metrics/model/sso/UserIdentity.java create mode 100644 backend/src/main/java/ervu_business_metrics/model/sso/UserSessionInfo.java create mode 100644 backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java create mode 100644 backend/src/main/java/ervu_business_metrics/service/SessionService.java create mode 100644 backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java create mode 100644 backend/src/main/java/exception/JsonParseException.java create mode 100644 backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Auth.java create mode 100644 backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Keys.java create mode 100644 backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Tables.java create mode 100644 backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/ActiveSession.java create mode 100644 backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/records/ActiveSessionRecord.java create mode 100644 backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml diff --git a/backend/src/main/java/AppConfig.java b/backend/src/main/java/AppConfig.java index d9b024a..641566e 100644 --- a/backend/src/main/java/AppConfig.java +++ b/backend/src/main/java/AppConfig.java @@ -22,7 +22,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.FilterType; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.web.client.RestTemplate; @@ -77,7 +76,7 @@ public class AppConfig { @Bean public LockProvider lockProvider(@Qualifier("datasource") DataSource dataSource) { - return new JdbcTemplateLockProvider(dataSource); + return new JdbcTemplateLockProvider(dataSource, "ervu_business_metrics.shedlock"); } @Bean diff --git a/backend/src/main/java/ervu_business_metrics/config/IdmReconcileEnabledCondition.java b/backend/src/main/java/ervu_business_metrics/config/KafkaEnabledCondition.java similarity index 66% rename from backend/src/main/java/ervu_business_metrics/config/IdmReconcileEnabledCondition.java rename to backend/src/main/java/ervu_business_metrics/config/KafkaEnabledCondition.java index 2af5f0d..171566c 100644 --- a/backend/src/main/java/ervu_business_metrics/config/IdmReconcileEnabledCondition.java +++ b/backend/src/main/java/ervu_business_metrics/config/KafkaEnabledCondition.java @@ -8,12 +8,12 @@ import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author Adel Kalimullin */ -public class IdmReconcileEnabledCondition implements Condition { - private static final String ERVU_RECONCILE_ENABLED = "ervu.idm.reconcile.enabled"; +public class KafkaEnabledCondition implements Condition { + private static final String ERVU_KAFKA_ENABLED = "ervu.kafka.enabled"; @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); - return Boolean.parseBoolean(env.getProperty(ERVU_RECONCILE_ENABLED, "true")); + return Boolean.parseBoolean(env.getProperty(ERVU_KAFKA_ENABLED, "true")); } } diff --git a/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java b/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java new file mode 100644 index 0000000..6660202 --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java @@ -0,0 +1,43 @@ +package ervu_business_metrics.dao; + +import java.util.List; + +import ervu_business_metrics.model.sso.UserAccount; +import ervu_business_metrics.model.sso.UserSessionInfo; +import org.jooq.DSLContext; +import org.springframework.stereotype.Repository; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.records.ActiveSessionRecord; + +import static ru.micord.webbpm.ervu.business_metrics.db_beans.auth.Tables.ACTIVE_SESSION; + +/** + * @author Adel Kalimullin + */ +@Repository +public class ActiveSessionDao { + private final DSLContext dsl; + + public ActiveSessionDao(DSLContext dsl) { + this.dsl = dsl; + } + + public void saveAll(List sessions) { + List records = sessions.stream() + .map(session -> { + UserAccount userAccount = session.getUserIdentity().getUserAccount(); + ActiveSessionRecord record = dsl.newRecord(ACTIVE_SESSION); + record.setSessionId(session.getSessionId()); + record.setDomainId(userAccount.getDomain().getId()); + record.setUserId(userAccount.getId()); + record.setIpAddress(session.getIp()); + return record; + }) + .toList(); + + dsl.batchInsert(records).execute(); + } + + public void clearActiveSessions() { + dsl.truncate(ACTIVE_SESSION).execute(); + } +} diff --git a/backend/src/main/java/ervu_business_metrics/dao/IdmDirectoriesDao.java b/backend/src/main/java/ervu_business_metrics/dao/IdmDirectoriesDao.java index 0a497ba..48b03f3 100644 --- a/backend/src/main/java/ervu_business_metrics/dao/IdmDirectoriesDao.java +++ b/backend/src/main/java/ervu_business_metrics/dao/IdmDirectoriesDao.java @@ -1,10 +1,9 @@ package ervu_business_metrics.dao; -import java.util.HashSet; import java.util.List; import java.util.Set; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; +import ervu_business_metrics.config.KafkaEnabledCondition; import org.jooq.DSLContext; import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Repository; @@ -22,7 +21,7 @@ import static ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.Tabl * @author Adel Kalimullin */ @Repository -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class IdmDirectoriesDao { private final DSLContext dsl; diff --git a/backend/src/main/java/ervu_business_metrics/kafka/KafkaConfig.java b/backend/src/main/java/ervu_business_metrics/kafka/KafkaConfig.java index 0fd473b..49352c3 100644 --- a/backend/src/main/java/ervu_business_metrics/kafka/KafkaConfig.java +++ b/backend/src/main/java/ervu_business_metrics/kafka/KafkaConfig.java @@ -3,11 +3,13 @@ package ervu_business_metrics.kafka; import java.util.HashMap; import java.util.Map; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; +import ervu_business_metrics.config.KafkaEnabledCondition; import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.config.SaslConfigs; import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -15,15 +17,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.config.KafkaListenerEndpointRegistry; -import org.springframework.kafka.core.ConsumerFactory; -import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.*; /** * @author Adel Kalimullin */ @Configuration @EnableKafka -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class KafkaConfig { @Value("${kafka.hosts}") private String bootstrapServers; @@ -68,4 +69,22 @@ public class KafkaConfig { factory.setConsumerFactory(consumerFactory()); return factory; } + + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap<>(); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol); + configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\"" + + username + "\" password=\"" + password + "\";"); + configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism); + return new DefaultKafkaProducerFactory<>(configProps); + } + + @Bean() + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } } diff --git a/backend/src/main/java/ervu_business_metrics/kafka/KafkaConsumerInitializer.java b/backend/src/main/java/ervu_business_metrics/kafka/KafkaConsumerInitializer.java index fa362fa..41abf7b 100644 --- a/backend/src/main/java/ervu_business_metrics/kafka/KafkaConsumerInitializer.java +++ b/backend/src/main/java/ervu_business_metrics/kafka/KafkaConsumerInitializer.java @@ -2,7 +2,7 @@ package ervu_business_metrics.kafka; import javax.annotation.PostConstruct; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; +import ervu_business_metrics.config.KafkaEnabledCondition; import ervu_business_metrics.service.IdmDirectoriesService; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.DependsOn; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; */ @Component @DependsOn("idmDirectoriesListener") -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class KafkaConsumerInitializer { private final IdmDirectoriesService idmDirectoriesService; diff --git a/backend/src/main/java/ervu_business_metrics/kafka/listener/IdmDirectoriesListener.java b/backend/src/main/java/ervu_business_metrics/kafka/listener/IdmDirectoriesListener.java index eda5cf5..d572a52 100644 --- a/backend/src/main/java/ervu_business_metrics/kafka/listener/IdmDirectoriesListener.java +++ b/backend/src/main/java/ervu_business_metrics/kafka/listener/IdmDirectoriesListener.java @@ -1,9 +1,9 @@ package ervu_business_metrics.kafka.listener; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; -import ervu_business_metrics.model.AccountData; -import ervu_business_metrics.model.DomainData; -import ervu_business_metrics.model.RoleData; +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.idm.AccountData; +import ervu_business_metrics.model.idm.DomainData; +import ervu_business_metrics.model.idm.RoleData; import ervu_business_metrics.service.IdmDirectoriesService; import org.springframework.context.annotation.Conditional; import org.springframework.kafka.annotation.KafkaListener; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; * @author Adel Kalimullin */ @Component -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class IdmDirectoriesListener { private final IdmDirectoriesService idmDirectoriesService; diff --git a/backend/src/main/java/ervu_business_metrics/kafka/listener/SessionResponseListener.java b/backend/src/main/java/ervu_business_metrics/kafka/listener/SessionResponseListener.java new file mode 100644 index 0000000..9d02d2c --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/kafka/listener/SessionResponseListener.java @@ -0,0 +1,42 @@ +package ervu_business_metrics.kafka.listener; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.sso.SessionStateResponse; +import ervu_business_metrics.service.SessionResponseHolder; +import exception.JsonParseException; +import org.springframework.context.annotation.Conditional; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +/** + * @author Adel Kalimullin + */ +@Component +@Conditional(KafkaEnabledCondition.class) +public class SessionResponseListener{ + private final ObjectMapper objectMapper; + private final SessionResponseHolder holder; + + public SessionResponseListener(ObjectMapper objectMapper, + SessionResponseHolder holder) { + this.objectMapper = objectMapper; + this.holder = holder; + } + + @KafkaListener(id = "${kafka.session.group.id}", topics = "${kafka.sso.sessions.state.response}") + public void listenKafkaSession(String kafkaMessage) { + SessionStateResponse response; + try { + response = objectMapper.readValue(kafkaMessage, SessionStateResponse.class); + } + catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + holder.initIfAbsent(response.getRequestKey(), response.getTotal()); + if (response.getUserSessionInfo() != null) { + holder.add(response.getRequestKey(), response.getUserSessionInfo()); + } + } +} diff --git a/backend/src/main/java/ervu_business_metrics/model/AccountData.java b/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java similarity index 93% rename from backend/src/main/java/ervu_business_metrics/model/AccountData.java rename to backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java index 938965a..c372c86 100644 --- a/backend/src/main/java/ervu_business_metrics/model/AccountData.java +++ b/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java @@ -1,11 +1,12 @@ -package ervu_business_metrics.model; +package ervu_business_metrics.model.idm; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import ervu_business_metrics.model.deserializer.ReferenceEntityDeserializer; +import ervu_business_metrics.model.ReferenceEntity; +import ervu_business_metrics.model.idm.deserializer.ReferenceEntityDeserializer; /** * @author Adel Kalimullin diff --git a/backend/src/main/java/ervu_business_metrics/model/DomainData.java b/backend/src/main/java/ervu_business_metrics/model/idm/DomainData.java similarity index 99% rename from backend/src/main/java/ervu_business_metrics/model/DomainData.java rename to backend/src/main/java/ervu_business_metrics/model/idm/DomainData.java index de3db50..997d7e7 100644 --- a/backend/src/main/java/ervu_business_metrics/model/DomainData.java +++ b/backend/src/main/java/ervu_business_metrics/model/idm/DomainData.java @@ -1,4 +1,4 @@ -package ervu_business_metrics.model; +package ervu_business_metrics.model.idm; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/backend/src/main/java/ervu_business_metrics/model/RoleData.java b/backend/src/main/java/ervu_business_metrics/model/idm/RoleData.java similarity index 97% rename from backend/src/main/java/ervu_business_metrics/model/RoleData.java rename to backend/src/main/java/ervu_business_metrics/model/idm/RoleData.java index 76466d2..00d2f4a 100644 --- a/backend/src/main/java/ervu_business_metrics/model/RoleData.java +++ b/backend/src/main/java/ervu_business_metrics/model/idm/RoleData.java @@ -1,4 +1,4 @@ -package ervu_business_metrics.model; +package ervu_business_metrics.model.idm; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/backend/src/main/java/ervu_business_metrics/model/deserializer/ReferenceEntityDeserializer.java b/backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java similarity index 94% rename from backend/src/main/java/ervu_business_metrics/model/deserializer/ReferenceEntityDeserializer.java rename to backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java index 43c244d..b61ef05 100644 --- a/backend/src/main/java/ervu_business_metrics/model/deserializer/ReferenceEntityDeserializer.java +++ b/backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java @@ -1,4 +1,4 @@ -package ervu_business_metrics.model.deserializer; +package ervu_business_metrics.model.idm.deserializer; import java.io.IOException; diff --git a/backend/src/main/java/ervu_business_metrics/model/sso/SessionStateResponse.java b/backend/src/main/java/ervu_business_metrics/model/sso/SessionStateResponse.java new file mode 100644 index 0000000..421688d --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/model/sso/SessionStateResponse.java @@ -0,0 +1,46 @@ +package ervu_business_metrics.model.sso; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Adel Kalimullin + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SessionStateResponse{ + private String requestKey; + private int total; + private int current; + private UserSessionInfo userSessionInfo; + + public String getRequestKey() { + return requestKey; + } + + public void setRequestKey(String requestKey) { + this.requestKey = requestKey; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public int getCurrent() { + return current; + } + + public void setCurrent(int current) { + this.current = current; + } + + public UserSessionInfo getUserSessionInfo() { + return userSessionInfo; + } + + public void setUserSessionInfo(UserSessionInfo userSessionInfo) { + this.userSessionInfo = userSessionInfo; + } +} diff --git a/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java b/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java new file mode 100644 index 0000000..e16de44 --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java @@ -0,0 +1,33 @@ +package ervu_business_metrics.model.sso; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import ervu_business_metrics.model.ReferenceEntity; +import ervu_business_metrics.model.idm.deserializer.ReferenceEntityDeserializer; + + +/** + * @author Adel Kalimullin + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserAccount { + private String id; + @JsonDeserialize(using = ReferenceEntityDeserializer.class) + private ReferenceEntity domain; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public ReferenceEntity getDomain() { + return domain; + } + + public void setDomain(ReferenceEntity domain) { + this.domain = domain; + } +} diff --git a/backend/src/main/java/ervu_business_metrics/model/sso/UserIdentity.java b/backend/src/main/java/ervu_business_metrics/model/sso/UserIdentity.java new file mode 100644 index 0000000..107a253 --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/model/sso/UserIdentity.java @@ -0,0 +1,19 @@ +package ervu_business_metrics.model.sso; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Adel Kalimullin + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserIdentity { + private UserAccount userAccount; + + public UserAccount getUserAccount() { + return userAccount; + } + + public void setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + } +} diff --git a/backend/src/main/java/ervu_business_metrics/model/sso/UserSessionInfo.java b/backend/src/main/java/ervu_business_metrics/model/sso/UserSessionInfo.java new file mode 100644 index 0000000..7b5f40d --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/model/sso/UserSessionInfo.java @@ -0,0 +1,37 @@ +package ervu_business_metrics.model.sso; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Adel Kalimullin + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserSessionInfo { + private String sessionId; + private UserIdentity userIdentity; + private String ip; + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public UserIdentity getUserIdentity() { + return userIdentity; + } + + public void setUserIdentity(UserIdentity userIdentity) { + this.userIdentity = userIdentity; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } +} diff --git a/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesDaoService.java b/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesDaoService.java index d25b6ea..12483b6 100644 --- a/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesDaoService.java +++ b/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesDaoService.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Set; import ervu_business_metrics.dao.IdmDirectoriesDao; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; +import ervu_business_metrics.config.KafkaEnabledCondition; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Conditional; @@ -17,7 +17,7 @@ import ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.tables.reco * @author Adel Kalimullin */ @Repository -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class IdmDirectoriesDaoService { private final IdmDirectoriesDao idmDirectoriesDao; diff --git a/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesService.java b/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesService.java index 93d2d08..8807ec1 100644 --- a/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesService.java +++ b/backend/src/main/java/ervu_business_metrics/service/IdmDirectoriesService.java @@ -7,12 +7,12 @@ import java.util.Map; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; +import ervu_business_metrics.config.KafkaEnabledCondition; import ervu_business_metrics.kafka.model.DeleteKafkaMessage; import ervu_business_metrics.kafka.model.UpsertMessage; -import ervu_business_metrics.model.AccountData; -import ervu_business_metrics.model.DomainData; -import ervu_business_metrics.model.RoleData; +import ervu_business_metrics.model.idm.AccountData; +import ervu_business_metrics.model.idm.DomainData; +import ervu_business_metrics.model.idm.RoleData; import ervu_business_metrics.service.processor.impl.AccountDataProcessor; import ervu_business_metrics.service.processor.DataProcessor; import ervu_business_metrics.service.processor.impl.DomainDataProcessor; @@ -38,7 +38,7 @@ import org.springframework.web.client.RestTemplate; */ @Component @DependsOn("liquibase") -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class IdmDirectoriesService { private static final Logger LOGGER = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass()); diff --git a/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java b/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java new file mode 100644 index 0000000..a323a6d --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java @@ -0,0 +1,74 @@ +package ervu_business_metrics.service; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.sso.UserSessionInfo; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +/** + * @author Adel Kalimullin + */ +@Component +@Conditional(KafkaEnabledCondition.class) +public class SessionResponseHolder { + private final Map sessionsMap = new ConcurrentHashMap<>(); + + public void initIfAbsent(String requestKey, int totalExpected) { + sessionsMap.computeIfAbsent(requestKey, key -> new SessionAccumulator(totalExpected)); + } + + public void add(String requestKey, UserSessionInfo sessionInfo) { + SessionAccumulator acc = sessionsMap.get(requestKey); + if (acc != null) { + acc.addSession(sessionInfo); + } + } + + public List awaitSessions(String requestKey, long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + SessionAccumulator acc = sessionsMap.get(requestKey); + if (acc == null) { + return List.of(); + } + return acc.getFuture().get(timeout, unit); + } + + public void remove(String requestKey) { + sessionsMap.remove(requestKey); + } + + private static class SessionAccumulator { + private final List sessions = new CopyOnWriteArrayList<>(); + private final CompletableFuture> future = new CompletableFuture<>(); + private final AtomicInteger current = new AtomicInteger(); + private final int totalExpected; + + public SessionAccumulator(int totalExpected) { + this.totalExpected = totalExpected; + if (totalExpected == 0) { + future.complete(List.of()); + } + } + + public void addSession(UserSessionInfo session) { + sessions.add(session); + if (current.incrementAndGet() == totalExpected) { + future.complete(sessions); + } + } + + public CompletableFuture> getFuture() { + return future; + } + } +} diff --git a/backend/src/main/java/ervu_business_metrics/service/SessionService.java b/backend/src/main/java/ervu_business_metrics/service/SessionService.java new file mode 100644 index 0000000..3734a86 --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/service/SessionService.java @@ -0,0 +1,69 @@ +package ervu_business_metrics.service; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.dao.ActiveSessionDao; +import ervu_business_metrics.model.sso.UserSessionInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Conditional; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +/** + * @author Adel Kalimullin + */ +@Component +@Conditional(KafkaEnabledCondition.class) +public class SessionService { + private final static Logger LOGGER = LoggerFactory.getLogger(SessionService.class); + private final KafkaTemplate kafkaTemplate; + private final SessionResponseHolder holder; + private final ActiveSessionDao activeSessionDao; + @Value("${session.fetch.timeout:15}") + private int timeout; + @Value("${kafka.sso.sessions.state.receive}") + private String sessionTopic; + + public SessionService( + KafkaTemplate kafkaTemplate, + SessionResponseHolder holder, ActiveSessionDao activeSessionDao) { + this.kafkaTemplate = kafkaTemplate; + this.holder = holder; + this.activeSessionDao = activeSessionDao; + } + + public void syncSessions() { + activeSessionDao.clearActiveSessions(); + + List sessions = fetchSessions(); + if (!sessions.isEmpty()) { + activeSessionDao.saveAll(sessions); + LOGGER.info("Синхронизировано {} сессий", sessions.size()); + } + else { + LOGGER.info("Нет сессий для синхронизации"); + } + } + + private List fetchSessions() { + String requestKey = UUID.randomUUID().toString(); + LOGGER.info("Отправка запроса с requestKey = {}", requestKey); + kafkaTemplate.send(sessionTopic, requestKey, "{}"); + + try { + return holder.awaitSessions(requestKey, timeout, TimeUnit.SECONDS); + } + catch (Exception e) { + LOGGER.error("Ошибка при получении сессий по ключу {}", requestKey, e); + return List.of(); + } + finally { + holder.remove(requestKey); + } + } +} diff --git a/backend/src/main/java/ervu_business_metrics/service/processor/impl/AccountDataProcessor.java b/backend/src/main/java/ervu_business_metrics/service/processor/impl/AccountDataProcessor.java index da87328..ef61126 100644 --- a/backend/src/main/java/ervu_business_metrics/service/processor/impl/AccountDataProcessor.java +++ b/backend/src/main/java/ervu_business_metrics/service/processor/impl/AccountDataProcessor.java @@ -6,8 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; -import ervu_business_metrics.model.AccountData; +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.idm.AccountData; import ervu_business_metrics.service.IdmDirectoriesDaoService; import ervu_business_metrics.service.processor.DataProcessor; import org.springframework.context.annotation.Conditional; @@ -19,7 +19,7 @@ import ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.tables.reco * @author Adel Kalimullin */ @Component -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class AccountDataProcessor implements DataProcessor { private final IdmDirectoriesDaoService idmDirectoriesDaoService; diff --git a/backend/src/main/java/ervu_business_metrics/service/processor/impl/DomainDataProcessor.java b/backend/src/main/java/ervu_business_metrics/service/processor/impl/DomainDataProcessor.java index fce5f7c..f04bcc8 100644 --- a/backend/src/main/java/ervu_business_metrics/service/processor/impl/DomainDataProcessor.java +++ b/backend/src/main/java/ervu_business_metrics/service/processor/impl/DomainDataProcessor.java @@ -6,8 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; -import ervu_business_metrics.model.DomainData; +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.idm.DomainData; import ervu_business_metrics.service.IdmDirectoriesDaoService; import ervu_business_metrics.service.processor.DataProcessor; import org.springframework.context.annotation.Conditional; @@ -18,7 +18,7 @@ import ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.tables.reco * @author Adel Kalimullin */ @Component -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class DomainDataProcessor implements DataProcessor { private final IdmDirectoriesDaoService idmDirectoriesDaoService; diff --git a/backend/src/main/java/ervu_business_metrics/service/processor/impl/RoleDataProcessor.java b/backend/src/main/java/ervu_business_metrics/service/processor/impl/RoleDataProcessor.java index d22e30c..afaa31f 100644 --- a/backend/src/main/java/ervu_business_metrics/service/processor/impl/RoleDataProcessor.java +++ b/backend/src/main/java/ervu_business_metrics/service/processor/impl/RoleDataProcessor.java @@ -6,8 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import ervu_business_metrics.config.IdmReconcileEnabledCondition; -import ervu_business_metrics.model.RoleData; +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.model.idm.RoleData; import ervu_business_metrics.service.IdmDirectoriesDaoService; import ervu_business_metrics.service.processor.DataProcessor; import org.springframework.context.annotation.Conditional; @@ -18,7 +18,7 @@ import ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.tables.reco * @author Adel Kalimullin */ @Component -@Conditional(IdmReconcileEnabledCondition.class) +@Conditional(KafkaEnabledCondition.class) public class RoleDataProcessor implements DataProcessor { private final IdmDirectoriesDaoService idmDirectoriesDaoService; diff --git a/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java b/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java new file mode 100644 index 0000000..e1cecd2 --- /dev/null +++ b/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java @@ -0,0 +1,38 @@ +package ervu_business_metrics.service.scheduler; + +import javax.annotation.PostConstruct; + +import ervu_business_metrics.config.KafkaEnabledCondition; +import ervu_business_metrics.service.SessionService; +import net.javacrumbs.shedlock.core.SchedulerLock; +import org.springframework.context.annotation.Conditional; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + + +/** + * @author Adel Kalimullin + */ +@Component +@Conditional(KafkaEnabledCondition.class) +public class SessionScheduledService { + private final SessionService sessionService; + + public SessionScheduledService(SessionService sessionService) { + this.sessionService = sessionService; + } + + @PostConstruct + @Transactional + public void init() { + run(); + } + + @Scheduled(cron = "${session.sync.cron:0 */10 * * * *}") + @SchedulerLock(name = "syncSessions") + @Transactional + public void run() { + sessionService.syncSessions(); + } +} diff --git a/backend/src/main/java/exception/JsonParseException.java b/backend/src/main/java/exception/JsonParseException.java new file mode 100644 index 0000000..46dfc21 --- /dev/null +++ b/backend/src/main/java/exception/JsonParseException.java @@ -0,0 +1,11 @@ +package exception; + +/** + * @author Adel Kalimullin + */ +public class JsonParseException extends RuntimeException { + + public JsonParseException(Throwable cause) { + super(cause); + } +} diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/DefaultCatalog.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/DefaultCatalog.java index 5b03d9a..51f5497 100644 --- a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/DefaultCatalog.java +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/DefaultCatalog.java @@ -13,6 +13,7 @@ import org.jooq.impl.CatalogImpl; import ru.micord.webbpm.ervu.business_metrics.db_beans.actualization.Actualization; import ru.micord.webbpm.ervu.business_metrics.db_beans.admin_indicators.AdminIndicators; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.Auth; import ru.micord.webbpm.ervu.business_metrics.db_beans.deregistration.Deregistration; import ru.micord.webbpm.ervu.business_metrics.db_beans.idm_reconcile.IdmReconcile; import ru.micord.webbpm.ervu.business_metrics.db_beans.init_registration_info.InitRegistrationInfo; @@ -47,6 +48,11 @@ public class DefaultCatalog extends CatalogImpl { */ public final AdminIndicators ADMIN_INDICATORS = AdminIndicators.ADMIN_INDICATORS; + /** + * The schema auth. + */ + public final Auth AUTH = Auth.AUTH; + /** * The schema deregistration. */ @@ -104,6 +110,7 @@ public class DefaultCatalog extends CatalogImpl { return Arrays.asList( Actualization.ACTUALIZATION, AdminIndicators.ADMIN_INDICATORS, + Auth.AUTH, Deregistration.DEREGISTRATION, IdmReconcile.IDM_RECONCILE, InitRegistrationInfo.INIT_REGISTRATION_INFO, diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Auth.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Auth.java new file mode 100644 index 0000000..193c038 --- /dev/null +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Auth.java @@ -0,0 +1,55 @@ +/* + * This file is generated by jOOQ. + */ +package ru.micord.webbpm.ervu.business_metrics.db_beans.auth; + + +import java.util.Arrays; +import java.util.List; + +import org.jooq.Catalog; +import org.jooq.Table; +import org.jooq.impl.SchemaImpl; + +import ru.micord.webbpm.ervu.business_metrics.db_beans.DefaultCatalog; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.ActiveSession; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Auth extends SchemaImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of auth + */ + public static final Auth AUTH = new Auth(); + + /** + * The table auth.active_session. + */ + public final ActiveSession ACTIVE_SESSION = ActiveSession.ACTIVE_SESSION; + + /** + * No further instances allowed + */ + private Auth() { + super("auth", null); + } + + + @Override + public Catalog getCatalog() { + return DefaultCatalog.DEFAULT_CATALOG; + } + + @Override + public final List> getTables() { + return Arrays.asList( + ActiveSession.ACTIVE_SESSION + ); + } +} diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Keys.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Keys.java new file mode 100644 index 0000000..cdff6e9 --- /dev/null +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Keys.java @@ -0,0 +1,28 @@ +/* + * This file is generated by jOOQ. + */ +package ru.micord.webbpm.ervu.business_metrics.db_beans.auth; + + +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.Internal; + +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.ActiveSession; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.records.ActiveSessionRecord; + + +/** + * A class modelling foreign key relationships and constraints of tables in + * auth. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Keys { + + // ------------------------------------------------------------------------- + // UNIQUE and PRIMARY KEY definitions + // ------------------------------------------------------------------------- + + public static final UniqueKey PK_ACTIVE_SESSION = Internal.createUniqueKey(ActiveSession.ACTIVE_SESSION, DSL.name("pk_active_session"), new TableField[] { ActiveSession.ACTIVE_SESSION.SESSION_ID, ActiveSession.ACTIVE_SESSION.DOMAIN_ID }, true); +} diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Tables.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Tables.java new file mode 100644 index 0000000..a956554 --- /dev/null +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/Tables.java @@ -0,0 +1,20 @@ +/* + * This file is generated by jOOQ. + */ +package ru.micord.webbpm.ervu.business_metrics.db_beans.auth; + + +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.ActiveSession; + + +/** + * Convenience access to all tables in auth. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Tables { + + /** + * The table auth.active_session. + */ + public static final ActiveSession ACTIVE_SESSION = ActiveSession.ACTIVE_SESSION; +} diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/ActiveSession.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/ActiveSession.java new file mode 100644 index 0000000..0137835 --- /dev/null +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/ActiveSession.java @@ -0,0 +1,233 @@ +/* + * This file is generated by jOOQ. + */ +package ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables; + + +import java.util.Collection; + +import org.jooq.Condition; +import org.jooq.Field; +import org.jooq.Name; +import org.jooq.PlainSQL; +import org.jooq.QueryPart; +import org.jooq.SQL; +import org.jooq.Schema; +import org.jooq.Select; +import org.jooq.Stringly; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.TableOptions; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.jooq.impl.TableImpl; + +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.Auth; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.Keys; +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.records.ActiveSessionRecord; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class ActiveSession extends TableImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of auth.active_session + */ + public static final ActiveSession ACTIVE_SESSION = new ActiveSession(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return ActiveSessionRecord.class; + } + + /** + * The column auth.active_session.session_id. + */ + public final TableField SESSION_ID = createField(DSL.name("session_id"), SQLDataType.VARCHAR(128).nullable(false), this, ""); + + /** + * The column auth.active_session.domain_id. + */ + public final TableField DOMAIN_ID = createField(DSL.name("domain_id"), SQLDataType.VARCHAR(128).nullable(false), this, ""); + + /** + * The column auth.active_session.user_id. + */ + public final TableField USER_ID = createField(DSL.name("user_id"), SQLDataType.VARCHAR(128).nullable(false), this, ""); + + /** + * The column auth.active_session.ip_address. + */ + public final TableField IP_ADDRESS = createField(DSL.name("ip_address"), SQLDataType.VARCHAR(64), this, ""); + + private ActiveSession(Name alias, Table aliased) { + this(alias, aliased, (Field[]) null, null); + } + + private ActiveSession(Name alias, Table aliased, Field[] parameters, Condition where) { + super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table(), where); + } + + /** + * Create an aliased auth.active_session table reference + */ + public ActiveSession(String alias) { + this(DSL.name(alias), ACTIVE_SESSION); + } + + /** + * Create an aliased auth.active_session table reference + */ + public ActiveSession(Name alias) { + this(alias, ACTIVE_SESSION); + } + + /** + * Create a auth.active_session table reference + */ + public ActiveSession() { + this(DSL.name("active_session"), null); + } + + @Override + public Schema getSchema() { + return aliased() ? null : Auth.AUTH; + } + + @Override + public UniqueKey getPrimaryKey() { + return Keys.PK_ACTIVE_SESSION; + } + + @Override + public ActiveSession as(String alias) { + return new ActiveSession(DSL.name(alias), this); + } + + @Override + public ActiveSession as(Name alias) { + return new ActiveSession(alias, this); + } + + @Override + public ActiveSession as(Table alias) { + return new ActiveSession(alias.getQualifiedName(), this); + } + + /** + * Rename this table + */ + @Override + public ActiveSession rename(String name) { + return new ActiveSession(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public ActiveSession rename(Name name) { + return new ActiveSession(name, null); + } + + /** + * Rename this table + */ + @Override + public ActiveSession rename(Table name) { + return new ActiveSession(name.getQualifiedName(), null); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession where(Condition condition) { + return new ActiveSession(getQualifiedName(), aliased() ? this : null, null, condition); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession where(Collection conditions) { + return where(DSL.and(conditions)); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession where(Condition... conditions) { + return where(DSL.and(conditions)); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession where(Field condition) { + return where(DSL.condition(condition)); + } + + /** + * Create an inline derived table from this table + */ + @Override + @PlainSQL + public ActiveSession where(SQL condition) { + return where(DSL.condition(condition)); + } + + /** + * Create an inline derived table from this table + */ + @Override + @PlainSQL + public ActiveSession where(@Stringly.SQL String condition) { + return where(DSL.condition(condition)); + } + + /** + * Create an inline derived table from this table + */ + @Override + @PlainSQL + public ActiveSession where(@Stringly.SQL String condition, Object... binds) { + return where(DSL.condition(condition, binds)); + } + + /** + * Create an inline derived table from this table + */ + @Override + @PlainSQL + public ActiveSession where(@Stringly.SQL String condition, QueryPart... parts) { + return where(DSL.condition(condition, parts)); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession whereExists(Select select) { + return where(DSL.exists(select)); + } + + /** + * Create an inline derived table from this table + */ + @Override + public ActiveSession whereNotExists(Select select) { + return where(DSL.notExists(select)); + } +} diff --git a/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/records/ActiveSessionRecord.java b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/records/ActiveSessionRecord.java new file mode 100644 index 0000000..1ba994d --- /dev/null +++ b/backend/src/main/java/ru/micord/webbpm/ervu/business_metrics/db_beans/auth/tables/records/ActiveSessionRecord.java @@ -0,0 +1,109 @@ +/* + * This file is generated by jOOQ. + */ +package ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.records; + + +import org.jooq.Record2; +import org.jooq.impl.UpdatableRecordImpl; + +import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.ActiveSession; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class ActiveSessionRecord extends UpdatableRecordImpl { + + private static final long serialVersionUID = 1L; + + /** + * Setter for auth.active_session.session_id. + */ + public void setSessionId(String value) { + set(0, value); + } + + /** + * Getter for auth.active_session.session_id. + */ + public String getSessionId() { + return (String) get(0); + } + + /** + * Setter for auth.active_session.domain_id. + */ + public void setDomainId(String value) { + set(1, value); + } + + /** + * Getter for auth.active_session.domain_id. + */ + public String getDomainId() { + return (String) get(1); + } + + /** + * Setter for auth.active_session.user_id. + */ + public void setUserId(String value) { + set(2, value); + } + + /** + * Getter for auth.active_session.user_id. + */ + public String getUserId() { + return (String) get(2); + } + + /** + * Setter for auth.active_session.ip_address. + */ + public void setIpAddress(String value) { + set(3, value); + } + + /** + * Getter for auth.active_session.ip_address. + */ + public String getIpAddress() { + return (String) get(3); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + @Override + public Record2 key() { + return (Record2) super.key(); + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached ActiveSessionRecord + */ + public ActiveSessionRecord() { + super(ActiveSession.ACTIVE_SESSION); + } + + /** + * Create a detached, initialised ActiveSessionRecord + */ + public ActiveSessionRecord(String sessionId, String domainId, String userId, String ipAddress) { + super(ActiveSession.ACTIVE_SESSION); + + setSessionId(sessionId); + setDomainId(domainId); + setUserId(userId); + setIpAddress(ipAddress); + resetChangedOnNotNull(); + } +} diff --git a/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml b/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml new file mode 100644 index 0000000..9932d2f --- /dev/null +++ b/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml @@ -0,0 +1,47 @@ + + + + + + add schema auth + + CREATE SCHEMA IF NOT EXISTS auth; + ALTER SCHEMA auth OWNER TO ervu_business_metrics; + + + + + add table active_session + + CREATE TABLE IF NOT EXISTS auth.active_session ( + session_id VARCHAR(128) NOT NULL, + domain_id VARCHAR(128) NOT NULL, + user_id VARCHAR(128) NOT NULL, + ip_address VARCHAR(64), + + CONSTRAINT pk_active_session PRIMARY KEY (session_id, domain_id) + ); + + ALTER TABLE auth.active_session OWNER TO ervu_business_metrics; + + + + + add shedlock table + + CREATE TABLE ervu_business_metrics.shedlock ( + name VARCHAR(255), + lock_until TIMESTAMP WITHOUT TIME ZONE, + locked_at TIMESTAMP WITHOUT TIME ZONE, + locked_by VARCHAR(255), + + CONSTRAINT shedlock_pk PRIMARY KEY (name) + ); + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/v_1.0/changelog-1.0.xml b/backend/src/main/resources/config/v_1.0/changelog-1.0.xml index 139f07d..31f04a2 100644 --- a/backend/src/main/resources/config/v_1.0/changelog-1.0.xml +++ b/backend/src/main/resources/config/v_1.0/changelog-1.0.xml @@ -31,6 +31,7 @@ + \ No newline at end of file diff --git a/config/micord.env b/config/micord.env index 6af5df5..9cb6bd8 100644 --- a/config/micord.env +++ b/config/micord.env @@ -31,5 +31,10 @@ KAFKA_DOMAIN_DELETED_GROUP_ID=ervu-business-metrics-backend-domain-deleted KAFKA_DOMAIN_DELETED=idmv2.domain.deleted KAFKA_ACCOUNT_DELETED_GROUP_ID=ervu-business-metrics-backend-account-deleted KAFKA_ACCOUNT_DELETED=idmv2.account.deleted -ERVU_IDM_RECONCILE_ENABLED=false -ERVU_IDM_URL=http://idm \ No newline at end of file +ERVU_KAFKA_ENABLED=false +ERVU_IDM_URL=http://idm +SESSION_SYNC_CRON=0 */10 * * * * +KAFKA_SSO_SESSIONS_STATE_RECEIVE=sso.session.state.receive +KAFKA_SSO_SESSIONS_STATE_RESPONSE=sso.sessions.state.response +KAFKA_SESSION_GROUP_ID=ervu-business-metrics-backend-session +SESSION_FETCH_TIMEOUT=15 \ No newline at end of file diff --git a/resources/src/main/resources/database/datasource.xml b/resources/src/main/resources/database/datasource.xml index de989c7..af41d21 100644 --- a/resources/src/main/resources/database/datasource.xml +++ b/resources/src/main/resources/database/datasource.xml @@ -9,6 +9,7 @@ 5432 actualization admin_indicators + auth deregistration idm_reconcile init_registration_info From bc0f2af3f13e49455bb5b60f100fb92508d4f06e Mon Sep 17 00:00:00 2001 From: "adel.kalimullin" Date: Tue, 13 May 2025 08:53:15 +0300 Subject: [PATCH 2/3] SUPPORT-9165: fix from review --- .../dao/ActiveSessionDao.java | 5 ++++- .../deserializer/ReferenceEntityDeserializer.java | 2 +- .../model/idm/AccountData.java | 2 +- .../model/sso/UserAccount.java | 2 +- .../service/SessionService.java | 2 +- .../service/scheduler/SessionScheduledService.java | 3 --- .../20250512-SUPPORT-9165-add_session_table.xml | 14 ++++++++++++++ 7 files changed, 22 insertions(+), 8 deletions(-) rename backend/src/main/java/ervu_business_metrics/{model/idm => }/deserializer/ReferenceEntityDeserializer.java (94%) diff --git a/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java b/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java index 6660202..dbd0aae 100644 --- a/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java +++ b/backend/src/main/java/ervu_business_metrics/dao/ActiveSessionDao.java @@ -2,9 +2,11 @@ package ervu_business_metrics.dao; import java.util.List; +import ervu_business_metrics.config.KafkaEnabledCondition; import ervu_business_metrics.model.sso.UserAccount; import ervu_business_metrics.model.sso.UserSessionInfo; import org.jooq.DSLContext; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Repository; import ru.micord.webbpm.ervu.business_metrics.db_beans.auth.tables.records.ActiveSessionRecord; @@ -14,6 +16,7 @@ import static ru.micord.webbpm.ervu.business_metrics.db_beans.auth.Tables.ACTIVE * @author Adel Kalimullin */ @Repository +@Conditional(KafkaEnabledCondition.class) public class ActiveSessionDao { private final DSLContext dsl; @@ -37,7 +40,7 @@ public class ActiveSessionDao { dsl.batchInsert(records).execute(); } - public void clearActiveSessions() { + public void clear() { dsl.truncate(ACTIVE_SESSION).execute(); } } diff --git a/backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java b/backend/src/main/java/ervu_business_metrics/deserializer/ReferenceEntityDeserializer.java similarity index 94% rename from backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java rename to backend/src/main/java/ervu_business_metrics/deserializer/ReferenceEntityDeserializer.java index b61ef05..d5aa220 100644 --- a/backend/src/main/java/ervu_business_metrics/model/idm/deserializer/ReferenceEntityDeserializer.java +++ b/backend/src/main/java/ervu_business_metrics/deserializer/ReferenceEntityDeserializer.java @@ -1,4 +1,4 @@ -package ervu_business_metrics.model.idm.deserializer; +package ervu_business_metrics.deserializer; import java.io.IOException; diff --git a/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java b/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java index c372c86..9c36953 100644 --- a/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java +++ b/backend/src/main/java/ervu_business_metrics/model/idm/AccountData.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import ervu_business_metrics.model.ReferenceEntity; -import ervu_business_metrics.model.idm.deserializer.ReferenceEntityDeserializer; +import ervu_business_metrics.deserializer.ReferenceEntityDeserializer; /** * @author Adel Kalimullin diff --git a/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java b/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java index e16de44..fcc948a 100644 --- a/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java +++ b/backend/src/main/java/ervu_business_metrics/model/sso/UserAccount.java @@ -3,7 +3,7 @@ package ervu_business_metrics.model.sso; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import ervu_business_metrics.model.ReferenceEntity; -import ervu_business_metrics.model.idm.deserializer.ReferenceEntityDeserializer; +import ervu_business_metrics.deserializer.ReferenceEntityDeserializer; /** diff --git a/backend/src/main/java/ervu_business_metrics/service/SessionService.java b/backend/src/main/java/ervu_business_metrics/service/SessionService.java index 3734a86..be1beac 100644 --- a/backend/src/main/java/ervu_business_metrics/service/SessionService.java +++ b/backend/src/main/java/ervu_business_metrics/service/SessionService.java @@ -38,7 +38,7 @@ public class SessionService { } public void syncSessions() { - activeSessionDao.clearActiveSessions(); + activeSessionDao.clear(); List sessions = fetchSessions(); if (!sessions.isEmpty()) { diff --git a/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java b/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java index e1cecd2..c005fe9 100644 --- a/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java +++ b/backend/src/main/java/ervu_business_metrics/service/scheduler/SessionScheduledService.java @@ -8,7 +8,6 @@ import net.javacrumbs.shedlock.core.SchedulerLock; import org.springframework.context.annotation.Conditional; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; /** @@ -24,14 +23,12 @@ public class SessionScheduledService { } @PostConstruct - @Transactional public void init() { run(); } @Scheduled(cron = "${session.sync.cron:0 */10 * * * *}") @SchedulerLock(name = "syncSessions") - @Transactional public void run() { sessionService.syncSessions(); } diff --git a/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml b/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml index 9932d2f..55c1030 100644 --- a/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml +++ b/backend/src/main/resources/config/v_1.0/20250512-SUPPORT-9165-add_session_table.xml @@ -27,6 +27,12 @@ ); ALTER TABLE auth.active_session OWNER TO ervu_business_metrics; + + COMMENT ON TABLE auth.active_session IS 'Активные сессии пользователей'; + COMMENT ON COLUMN auth.active_session.session_id IS 'Идентификатор сессии'; + COMMENT ON COLUMN auth.active_session.domain_id IS 'Идентификатор домена'; + COMMENT ON COLUMN auth.active_session.user_id IS 'Идентификатор пользователя'; + COMMENT ON COLUMN auth.active_session.ip_address IS 'IP-адрес пользователя'; @@ -41,6 +47,14 @@ CONSTRAINT shedlock_pk PRIMARY KEY (name) ); + + ALTER TABLE ervu_business_metrics.shedlock OWNER TO ervu_business_metrics; + + COMMENT ON TABLE ervu_business_metrics.shedlock IS 'Таблица для хранения блокировок ShedLock'; + COMMENT ON COLUMN ervu_business_metrics.shedlock.name IS 'Имя ресурса блокировки'; + COMMENT ON COLUMN ervu_business_metrics.shedlock.lock_until IS 'Время, до которого действует блокировка'; + COMMENT ON COLUMN ervu_business_metrics.shedlock.locked_at IS 'Время создания блокировки'; + COMMENT ON COLUMN ervu_business_metrics.shedlock.locked_by IS 'Идентификатор узла, установившего блокировку'; From 682d9313dc86e089979c11392f6c4bcf44608208 Mon Sep 17 00:00:00 2001 From: "adel.kalimullin" Date: Tue, 13 May 2025 14:59:59 +0300 Subject: [PATCH 3/3] SUPPORT-9165: add wait for accumulator --- .../service/SessionResponseHolder.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java b/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java index a323a6d..c43f2e2 100644 --- a/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java +++ b/backend/src/main/java/ervu_business_metrics/service/SessionResponseHolder.java @@ -12,6 +12,8 @@ import java.util.concurrent.atomic.AtomicInteger; import ervu_business_metrics.config.KafkaEnabledCondition; import ervu_business_metrics.model.sso.UserSessionInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Component; @@ -21,6 +23,7 @@ import org.springframework.stereotype.Component; @Component @Conditional(KafkaEnabledCondition.class) public class SessionResponseHolder { + private final Logger LOGGER = LoggerFactory.getLogger(SessionResponseHolder.class); private final Map sessionsMap = new ConcurrentHashMap<>(); public void initIfAbsent(String requestKey, int totalExpected) { @@ -37,9 +40,21 @@ public class SessionResponseHolder { public List awaitSessions(String requestKey, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { SessionAccumulator acc = sessionsMap.get(requestKey); + + long startTime = System.nanoTime(); + LOGGER.info("Начало ожидания сессий для requestKey = {}", requestKey); + + while (acc == null && (System.nanoTime() - startTime) < unit.toNanos(timeout)) { + LOGGER.debug("Аккумулятор для requestKey = {} еще не найден, повторная проверка", requestKey); + TimeUnit.MILLISECONDS.sleep(50); + acc = sessionsMap.get(requestKey); + } + if (acc == null) { + LOGGER.error("Аккумулятор для requestKey: {} не был инициализирован за время ожидания.", requestKey); return List.of(); } + return acc.getFuture().get(timeout, unit); }