From 0e95f189745a891fb768135708128949aa266526 Mon Sep 17 00:00:00 2001 From: Ivan Nikolaev-Axenov Date: Mon, 30 Sep 2024 18:14:49 +0300 Subject: [PATCH] http://jira.mfd.msk:8088/browse/CLS-758 kafka support added, new tasks created, added opencsv, controllers removed --- clearing-parent/control-service/pom.xml | 33 ++++- .../clearing/control/config/ImdgConfig.java | 58 ++++++++ .../clearing/control/config/KafkaConfig.java | 30 ++++ .../control/config/KafkaSenderConfig.java | 57 +++++++ .../settings/ControlServiceSettings.java | 35 +++++ .../control/controller/ControlController.java | 33 ----- .../control/model/ResultContainer.java | 140 +++++++++++++++++- .../control/repository/ControlRepository.java | 48 ++++-- .../control/service/CommandService.java | 32 ++++ .../control/service/ControlService.java | 103 ++++++------- .../control/util/CustomMappingStrategy.java | 52 +++++++ .../src/main/resources/application.properties | 24 ++- .../ru/spcex/platform/enumeration/Task.java | 3 + .../cud/clearing/ControlServiceRequest.java | 27 ++++ 14 files changed, 566 insertions(+), 109 deletions(-) create mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/ImdgConfig.java create mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaConfig.java create mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaSenderConfig.java delete mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/controller/ControlController.java create mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/CommandService.java create mode 100644 clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/util/CustomMappingStrategy.java create mode 100644 platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/ControlServiceRequest.java diff --git a/clearing-parent/control-service/pom.xml b/clearing-parent/control-service/pom.xml index 3fed973f9..56887fec3 100644 --- a/clearing-parent/control-service/pom.xml +++ b/clearing-parent/control-service/pom.xml @@ -16,14 +16,15 @@ + 5.5.2 7.0.1 - + org.springframework.boot - spring-boot-starter-web + spring-boot-starter @@ -40,6 +41,23 @@ com.mchange c3p0 + + ru.spcex.platform + platform-imdg-api-hazelcast-impl + + + + + com.opencsv + opencsv + ${opencsv.version} + + + + + ru.spcex.platform + platform-messaging + @@ -47,21 +65,24 @@ logstash-logback-encoder ${logstash-logback-encoder.version} - - com.fasterxml.jackson.core - jackson-databind - + ru.spcex.platform platform-enum + org.springframework.boot spring-boot-starter-test test + + ru.spcex.clearing + test-clearing + test + diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/ImdgConfig.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/ImdgConfig.java new file mode 100644 index 000000000..c7129f970 --- /dev/null +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/ImdgConfig.java @@ -0,0 +1,58 @@ +package ru.spcex.clearing.control.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import ru.spcex.clearing.control.config.settings.ControlServiceSettings; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams; +import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService; + +@Configuration +public class ImdgConfig { + @Bean("taskExecutorHazelcastClientInitializer") + public ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer() { + return createThreadPoolTaskExecutor(1, true); + } + + @Bean("taskExecutorIdGeneratorAwaiter") + public ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter() { + return createThreadPoolTaskExecutor(1, false); + } + + @Bean("imdgProvider") + public ImdgProvider imdgProvider(@Qualifier("taskExecutorHazelcastClientInitializer") ThreadPoolTaskExecutor taskExecutorHazelcastClientInitializer, + @Qualifier("taskExecutorIdGeneratorAwaiter") ThreadPoolTaskExecutor taskExecutorIdGeneratorAwaiter, + ControlServiceSettings settings) { + HazelcastClientParams params = new HazelcastClientParams(); + params.setClusterMembers(settings.getHazelcast().getClusterMembers()); + params.setLogin(settings.getHazelcast().getLogin()); + params.setPassword(settings.getHazelcast().getPassword()); + return new HazelcastService(taskExecutorHazelcastClientInitializer, taskExecutorIdGeneratorAwaiter, params); + } + + @Bean("executor") + public ThreadPoolTaskExecutor executor(ControlServiceSettings settings) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(10); + executor.setCorePoolSize(10); + executor.setThreadNamePrefix("control-service"); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(300); + executor.initialize(); + return executor; + } + + private static ThreadPoolTaskExecutor createThreadPoolTaskExecutor(int maxPoolSz, boolean waitForCompletion) { + ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor(); + if (maxPoolSz > 2) { + pool.setKeepAliveSeconds(60); + pool.setAllowCoreThreadTimeOut(true); + } + pool.setCorePoolSize(maxPoolSz); + pool.setWaitForTasksToCompleteOnShutdown(waitForCompletion); + return pool; + } + +} diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaConfig.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaConfig.java new file mode 100644 index 000000000..73f81de6c --- /dev/null +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaConfig.java @@ -0,0 +1,30 @@ +package ru.spcex.clearing.control.config; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.producer.Producer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import ru.spcex.clearing.control.config.settings.ControlServiceSettings; +import ru.spcex.clearing.platform.messaging.config.KafkaConsumerFactory; +import ru.spcex.clearing.platform.messaging.config.KafkaProducerFactory; + +@Configuration +public class KafkaConfig { + @Autowired + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + @Bean + public Consumer createConsumer(ControlServiceSettings settings) { + return KafkaConsumerFactory.consumer(settings.getKafkaConsumer()); + } + + @Autowired + @Bean + public Producer createProducer(ControlServiceSettings settings) { + return settings.getKafkaProducer() != null ? + KafkaProducerFactory.producer(settings.getKafkaProducer()) : + null; + } +} diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaSenderConfig.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaSenderConfig.java new file mode 100644 index 000000000..0855112c1 --- /dev/null +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/KafkaSenderConfig.java @@ -0,0 +1,57 @@ +package ru.spcex.clearing.control.config; + +import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import ru.spcex.clearing.control.config.settings.ControlServiceSettings; +import ru.spcex.clearing.platform.messaging.config.KafkaProducerFactory; +import ru.spcex.clearing.platform.messaging.config.element.KafkaProducerSettings; +import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender; +import ru.spcex.platform.imdg.api.ImdgId; +import ru.spcex.platform.imdg.api.ImdgProvider; + +@Configuration +public class KafkaSenderConfig { + Logger log = LoggerFactory.getLogger(getClass()); + + private final ImdgProvider imdgProvider; + + @Autowired + public KafkaSenderConfig(ImdgProvider imdgProvider) { + this.imdgProvider = imdgProvider; + } + + @Bean + public ProducerFactory pf(ControlServiceSettings settings) { + if (settings.getKafkaProducer() == null) { + return null; + } + KafkaProducerSettings kafkaSettings = settings.getKafkaProducer(); + return KafkaProducerFactory.producerFactory(kafkaSettings); + } + + @Bean("kafkaTemplate") + public KafkaTemplate kafkaTemplate(ProducerFactory pf) { + if (pf == null) { + return null; + } + return new KafkaTemplate<>(pf); + } + + @Bean + public Supplier kafkaSenderSupplier(KafkaTemplate kafkaTemplate, + ImdgProvider imdgProvider) { + ImdgId imdgIdGenerator = imdgProvider.getImdgIdGenerator(); + return () -> KafkaSender + .setup() + .setKafkaTemplate(kafkaTemplate) + .idGenerator(imdgIdGenerator::nextId) + .saveRequestInfo(false) + .build(); + } +} diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/settings/ControlServiceSettings.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/settings/ControlServiceSettings.java index 6a3e73ceb..5116d326d 100644 --- a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/settings/ControlServiceSettings.java +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/config/settings/ControlServiceSettings.java @@ -3,6 +3,9 @@ package ru.spcex.clearing.control.config.settings; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.stereotype.Component; +import ru.spcex.clearing.platform.messaging.config.element.KafkaConsumerSettings; +import ru.spcex.clearing.platform.messaging.config.element.KafkaProducerSettings; +import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams; @Component @ConfigurationProperties("control-service") @@ -10,6 +13,14 @@ public class ControlServiceSettings { @NestedConfigurationProperty private Database database; + @NestedConfigurationProperty + private HazelcastClientParams hazelcast; + + @NestedConfigurationProperty + private KafkaConsumerSettings kafkaConsumer; + @NestedConfigurationProperty + private KafkaProducerSettings kafkaProducer; + private String resultDirectory; public Database getDatabase() { @@ -20,6 +31,30 @@ public class ControlServiceSettings { this.database = database; } + public HazelcastClientParams getHazelcast() { + return hazelcast; + } + + public void setHazelcast(HazelcastClientParams hazelcast) { + this.hazelcast = hazelcast; + } + + public KafkaConsumerSettings getKafkaConsumer() { + return kafkaConsumer; + } + + public void setKafkaConsumer(KafkaConsumerSettings kafkaConsumer) { + this.kafkaConsumer = kafkaConsumer; + } + + public KafkaProducerSettings getKafkaProducer() { + return kafkaProducer; + } + + public void setKafkaProducer(KafkaProducerSettings kafkaProducer) { + this.kafkaProducer = kafkaProducer; + } + public String getResultDirectory() { return resultDirectory; } diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/controller/ControlController.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/controller/ControlController.java deleted file mode 100644 index 72ee5e8aa..000000000 --- a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/controller/ControlController.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.spcex.clearing.control.controller; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import ru.spcex.clearing.control.service.ControlService; -import ru.spcex.platform.enumeration.SessionType; - -@RestController -@RequestMapping("/api/v1/control") -public class ControlController { - private final ControlService controlService; - - public ControlController(ControlService controlService) { - this.controlService = controlService; - } - - @GetMapping("/general-control/{sessionType}") - public String generalControl(@PathVariable("sessionType") SessionType sessionType) { - return controlService.generalControl(sessionType); - } - - @GetMapping("/internal-control/{sessionId}") - public String internalControl(@PathVariable("sessionId") Long sessionId) { - return controlService.internalControl(sessionId); - } - - @GetMapping("/internal-control-finl/{sessionId}") - public String internalControlFinl(@PathVariable("sessionId") Long sessionId) { - return controlService.internalControlFinl(sessionId); - } -} diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/model/ResultContainer.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/model/ResultContainer.java index 63894e134..152373461 100644 --- a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/model/ResultContainer.java +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/model/ResultContainer.java @@ -1,4 +1,142 @@ package ru.spcex.clearing.control.model; -public record ResultContainer(Long firstTable, Long secondTable, Long difference) { +import com.opencsv.bean.CsvBindByName; +import com.opencsv.bean.CsvBindByPosition; +import java.util.Objects; +import ru.spcex.platform.enumeration.SessionType; + +public class ResultContainer { + @CsvBindByName(column = "sessionId") + @CsvBindByPosition(position = 0) + private Long sessionId; + + @CsvBindByName(column = "sessionType") + @CsvBindByPosition(position = 1) + private SessionType sessionType; + + @CsvBindByName(column = "firstTableTitle") + @CsvBindByPosition(position = 2) + private String firstTableTitle; + + @CsvBindByName(column = "firstTableRecordQuantity") + @CsvBindByPosition(position = 3) + private Long firstTableRecordQuantity; + + @CsvBindByName(column = "secondTableRecordTitle") + @CsvBindByPosition(position = 4) + private String secondTableRecordTitle; + + @CsvBindByName(column = "secondTableRecordQuantity") + @CsvBindByPosition(position = 5) + private Long secondTableRecordQuantity; + + @CsvBindByName(column = "quantityDifference") + @CsvBindByPosition(position = 6) + private Long quantityDifference; + + public ResultContainer(Long sessionId, + SessionType sessionType, + String firstTableTitle, + Long firstTableRecordQuantity, + String secondTableRecordTitle, + Long secondTableRecordQuantity, + Long quantityDifference) { + this.sessionId = sessionId; + this.sessionType = sessionType; + this.firstTableTitle = firstTableTitle; + this.firstTableRecordQuantity = firstTableRecordQuantity; + this.secondTableRecordTitle = secondTableRecordTitle; + this.secondTableRecordQuantity = secondTableRecordQuantity; + this.quantityDifference = quantityDifference; + } + + public ResultContainer(Long firstTableRecordQuantity, + Long secondTableRecordQuantity, + Long quantityDifference) { + this.firstTableRecordQuantity = firstTableRecordQuantity; + this.secondTableRecordQuantity = secondTableRecordQuantity; + this.quantityDifference = quantityDifference; + } + + public Long getSessionId() { + return sessionId; + } + + public void setSessionId(Long sessionId) { + this.sessionId = sessionId; + } + + public SessionType getSessionType() { + return sessionType; + } + + public void setSessionType(SessionType sessionType) { + this.sessionType = sessionType; + } + + public String getFirstTableTitle() { + return firstTableTitle; + } + + public void setFirstTableTitle(String firstTableTitle) { + this.firstTableTitle = firstTableTitle; + } + + public Long getFirstTableRecordQuantity() { + return firstTableRecordQuantity; + } + + public void setFirstTableRecordQuantity(Long firstTableRecordQuantity) { + this.firstTableRecordQuantity = firstTableRecordQuantity; + } + + public String getSecondTableRecordTitle() { + return secondTableRecordTitle; + } + + public void setSecondTableRecordTitle(String secondTableRecordTitle) { + this.secondTableRecordTitle = secondTableRecordTitle; + } + + public Long getSecondTableRecordQuantity() { + return secondTableRecordQuantity; + } + + public void setSecondTableRecordQuantity(Long secondTableRecordQuantity) { + this.secondTableRecordQuantity = secondTableRecordQuantity; + } + + public Long getQuantityDifference() { + return quantityDifference; + } + + public void setQuantityDifference(Long quantityDifference) { + this.quantityDifference = quantityDifference; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResultContainer that = (ResultContainer) o; + return Objects.equals(sessionId, that.sessionId) && sessionType == that.sessionType && Objects.equals(firstTableTitle, that.firstTableTitle) && Objects.equals(firstTableRecordQuantity, that.firstTableRecordQuantity) && Objects.equals(secondTableRecordTitle, that.secondTableRecordTitle) && Objects.equals(secondTableRecordQuantity, that.secondTableRecordQuantity) && Objects.equals(quantityDifference, that.quantityDifference); + } + + @Override + public int hashCode() { + return Objects.hash(sessionId, sessionType, firstTableTitle, firstTableRecordQuantity, secondTableRecordTitle, secondTableRecordQuantity, quantityDifference); + } + + @Override + public String toString() { + return "ResultContainer{" + + "sessionId=" + sessionId + + ", sessionType=" + sessionType + + ", firstTableTitle='" + firstTableTitle + '\'' + + ", firstTableRecordQuantity=" + firstTableRecordQuantity + + ", secondTableRecordTitle='" + secondTableRecordTitle + '\'' + + ", secondTableRecordQuantity=" + secondTableRecordQuantity + + ", quantityDifference=" + quantityDifference + + '}'; + } } diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/repository/ControlRepository.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/repository/ControlRepository.java index 990be1513..707f537ec 100644 --- a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/repository/ControlRepository.java +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/repository/ControlRepository.java @@ -25,43 +25,61 @@ public class ControlRepository { @Transactional public ResultContainer getFinlCountDiff() { String sqlQuery = """ - WITH s_trades_count AS (SELECT COUNT(*) FROM s_trades), - execution_deposit_count AS (SELECT COUNT(*) FROM execution_deposit) + WITH s_trades_count AS (SELECT COUNT(*) FROM s_trades WHERE CAST(created_at AS DATE) = CAST(NOW() AS DATE)), + execution_deposit_count AS (SELECT COUNT(*) FROM execution_deposit WHERE CAST(created_at AS DATE) = CAST(NOW() AS DATE)) SELECT (SELECT * FROM s_trades_count) AS first_table, (SELECT * FROM execution_deposit_count) AS second_table, (SELECT s_trades_count.count - execution_deposit_count.count) AS diff FROM s_trades_count, execution_deposit_count; """; - return jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + ResultContainer resultContainer = jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + if (resultContainer != null) { + resultContainer.setFirstTableTitle("s_trades"); + resultContainer.setSecondTableRecordTitle("execution_deposit"); + } + + return resultContainer; } @Transactional public ResultContainer getIpoTrdCountDiff() { String sqlQuery = """ - WITH s_trades_fond_count AS (SELECT COUNT(*) FROM s_trades WHERE section = 'FOND'), - execution_fond_count AS (SELECT COUNT(*) FROM execution_fond) + WITH s_trades_fond_count AS (SELECT COUNT(*) FROM s_trades WHERE section = 'FOND' AND CAST(created_at AS DATE) = CAST(NOW() AS DATE)), + execution_fond_count AS (SELECT COUNT(*) FROM execution_fond WHERE CAST(created_at AS DATE) = CAST(NOW() AS DATE)) SELECT (SELECT * FROM s_trades_fond_count) AS first_table, (SELECT * FROM execution_fond_count) AS second_table, (SELECT s_trades_fond_count.count - execution_fond_count.count) AS diff FROM s_trades_fond_count, execution_fond_count; """; - return jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + ResultContainer resultContainer = jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + if (resultContainer != null) { + resultContainer.setFirstTableTitle("s_trades"); + resultContainer.setSecondTableRecordTitle("execution_fond"); + } + + return resultContainer; } @Transactional public ResultContainer getCurrInitCountDiff() { String sqlQuery = """ - WITH s_trades_curr_count AS (SELECT COUNT(*) FROM s_trades WHERE section = 'CURR'), - execution_currency_count AS (SELECT COUNT(*) FROM execution_currency) + WITH s_trades_curr_count AS (SELECT COUNT(*) FROM s_trades WHERE section = 'CURR' AND CAST(created_at AS DATE) = CAST(NOW() AS DATE)), + execution_currency_count AS (SELECT COUNT(*) FROM execution_currency WHERE CAST(created_at AS DATE) = CAST(NOW() AS DATE)) SELECT (SELECT * FROM s_trades_curr_count) AS first_table, (SELECT * FROM execution_currency_count) AS second_table, (SELECT s_trades_curr_count.count - execution_currency_count.count) AS diff FROM s_trades_curr_count, execution_currency_count; """; - return jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + ResultContainer resultContainer = jdbcTemplate.queryForObject(sqlQuery, resultContainerMapper); + if (resultContainer != null) { + resultContainer.setFirstTableTitle("s_trades"); + resultContainer.setSecondTableRecordTitle("execution_currency"); + } + + return resultContainer; } @Transactional @@ -72,10 +90,10 @@ public class ControlRepository { @Transactional public ResultContainer getFinlInternalCountDiff(Long sessionId) { String sqlQuery = """ - WITH payment_instruction_ids AS (SELECT id FROM payment_instruction WHERE session_id = :sessionId), + WITH payment_instruction_ids AS (SELECT id FROM payment_instruction WHERE session_id = :sessionId AND CAST(created_at AS DATE) = CAST(NOW() AS DATE)), s_df03_ids AS (SELECT s_df03.id FROM payment_instruction_ids - LEFT JOIN s_df03 ON payment_instruction_ids.id = s_df03.payment_instruction_id), + LEFT JOIN s_df03 ON payment_instruction_ids.id = s_df03.payment_instruction_id AND CAST(generation_time AS DATE) = CAST(NOW() AS DATE)), payment_instruction_count AS (SELECT COUNT(*) FROM payment_instruction_ids), s_df03_count AS (SELECT COUNT(*) FROM s_df03_ids) SELECT (SELECT payment_instruction_count.count FROM payment_instruction_count) AS first_table, @@ -84,8 +102,14 @@ public class ControlRepository { FROM payment_instruction_count, s_df03_count; """; - return namedParameterJdbcTemplate.queryForObject(sqlQuery, + ResultContainer resultContainer = namedParameterJdbcTemplate.queryForObject(sqlQuery, new MapSqlParameterSource("sessionId", sessionId), resultContainerMapper); + if (resultContainer != null) { + resultContainer.setFirstTableTitle("payment_instruction"); + resultContainer.setSecondTableRecordTitle("s_df03"); + } + + return resultContainer; } } diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/CommandService.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/CommandService.java new file mode 100644 index 000000000..3d80698b2 --- /dev/null +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/CommandService.java @@ -0,0 +1,32 @@ +package ru.spcex.clearing.control.service; + +import org.apache.kafka.clients.consumer.Consumer; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; +import ru.spcex.clearing.platform.messaging.domain.cud.clearing.ControlServiceRequest; +import ru.spcex.clearing.platform.messaging.service.QueueConsumer; +import ru.spcex.platform.enumeration.Task; + +@Service +public class CommandService extends QueueConsumer implements InitializingBean { + private final ControlService controlService; + + public CommandService(Consumer kafkaQueue, ControlService controlService) { + super(kafkaQueue); + this.controlService = controlService; + } + + @Override + public void afterPropertiesSet() throws Exception { + callback(ControlServiceRequest.class) + .setConsumer(request -> controlService.generalControl(request.getRequestPayload().getSessionType())) + .forDestination(Task.generalControl_GLCL.topic(), callbacks::put); + callback(ControlServiceRequest.class) + .setConsumer(request -> controlService.internalControl(request.getRequestPayload().getSessionId())) + .forDestination(Task.internalControl_ILCL.topic(), callbacks::put); + callback(ControlServiceRequest.class) + .setConsumer(request -> controlService.internalControlFinl(request.getRequestPayload().getSessionId())) + .forDestination(Task.internalFinlControl_IFCL.topic(), callbacks::put); + init(); + } +} diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/ControlService.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/ControlService.java index a16223bcb..783a4a908 100644 --- a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/ControlService.java +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/service/ControlService.java @@ -1,19 +1,27 @@ package ru.spcex.clearing.control.service; +import com.opencsv.bean.StatefulBeanToCsv; +import com.opencsv.bean.StatefulBeanToCsvBuilder; +import com.opencsv.exceptions.CsvDataTypeMismatchException; +import com.opencsv.exceptions.CsvRequiredFieldEmptyException; import java.io.FileWriter; import java.io.IOException; -import java.io.PrintWriter; +import java.io.Writer; import java.nio.file.Path; import java.time.Instant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import ru.spcex.clearing.control.config.settings.ControlServiceSettings; import ru.spcex.clearing.control.model.ResultContainer; import ru.spcex.clearing.control.repository.ControlRepository; +import ru.spcex.clearing.control.util.CustomMappingStrategy; import ru.spcex.platform.enumeration.SessionType; -import ru.spcex.platform.utils.collection.Pair; @Service public class ControlService { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final ControlRepository controlRepository; private final ControlServiceSettings settings; private final Path resultDirectory; @@ -25,73 +33,58 @@ public class ControlService { resultDirectory = Path.of(settings.getResultDirectory()); } - public String generalControl(SessionType sessionType) { - Pair tables; - ResultContainer resultContainer; + public void generalControl(SessionType sessionType) { + log.info("Starting general control with session type {}", sessionType); - if (sessionType.equals(SessionType.FINL)) { - tables = new Pair<>("sTrade", "executionDeposit"); - resultContainer = controlRepository.getFinlCountDiff(); - } else if (sessionType.equals(SessionType.IPOB) || sessionType.equals(SessionType.IPOT) || sessionType.equals(SessionType.IPO0) || sessionType.equals(SessionType.TRDT)) { - tables = new Pair<>("sTradeFond", "executionFond"); - resultContainer = controlRepository.getIpoTrdCountDiff(); - } else if (sessionType.equals(SessionType.CURR)) { - tables = new Pair<>("sTradeCurrency", "executionCurrency"); - resultContainer = controlRepository.getCurrInitCountDiff(); - } else { - throw new IllegalStateException("Unexpected value: " + sessionType); + ResultContainer resultContainer = switch (sessionType) { + case FINL -> controlRepository.getFinlCountDiff(); + case IPOB, IPOT, IPO0, TRDT -> controlRepository.getIpoTrdCountDiff(); + case CURR -> controlRepository.getCurrInitCountDiff(); + default -> { + log.error("Unknown session type {}", sessionType); + throw new IllegalStateException("Unexpected value: " + sessionType); + } + }; + + if (resultContainer.getQuantityDifference() != 0) { + resultContainer.setSessionType(sessionType); + writeToCsvFile(resultContainer); } - - if (resultContainer.difference() != 0) { - writeToCsvFile(sessionType, resultContainer, tables); - } - - return resultContainer.toString(); } - public String internalControl(Long sessionId) { + public void internalControl(Long sessionId) { + log.info("Starting internal control with session id {}", sessionId); + ResultContainer resultContainer = controlRepository.getInternalCountDiff(sessionId); - if (resultContainer.difference() != 0) { - writeToCsvFileInternal(sessionId, resultContainer); + if (resultContainer.getQuantityDifference() != 0) { + resultContainer.setSessionId(sessionId); + writeToCsvFile(resultContainer); } - - return resultContainer.toString(); } - public String internalControlFinl(Long sessionId) { + public void internalControlFinl(Long sessionId) { + log.info("Starting internal control FINL with session id {}", sessionId); + ResultContainer resultContainer = controlRepository.getFinlInternalCountDiff(sessionId); - if (resultContainer.difference() != 0) { - writeToCsvFileInternal(sessionId, resultContainer); - } - - return resultContainer.toString(); - } - - private void writeToCsvFile(SessionType sessionType, ResultContainer resultContainer, Pair tables) { - try (PrintWriter printWriter = new PrintWriter(new FileWriter(resultDirectory.resolve("result-" + sessionType.name() + "-" + Instant.now().getEpochSecond() + ".csv").toFile()))) { - printWriter.printf("session_type,%s,%s,diff%n", tables.getFirst(), tables.getSecond()); - printWriter.printf("%s,%s,%s,%s%n", - sessionType.name(), - resultContainer.firstTable(), - resultContainer.secondTable(), - resultContainer.difference()); - } catch (IOException e) { - throw new RuntimeException(e); + if (resultContainer.getQuantityDifference() != 0) { + resultContainer.setSessionId(sessionId); + writeToCsvFile(resultContainer); } } - private void writeToCsvFileInternal(Long sessionId, ResultContainer resultContainer) { - try (PrintWriter printWriter = new PrintWriter(new FileWriter(resultDirectory.resolve("result-internal-" + sessionId + "-" + Instant.now().getEpochSecond() + ".csv").toFile()))) { - printWriter.println("session_id,paymentInstruction,s_df03,diff"); - printWriter.printf("%s,%s,%s,%s%n", - sessionId, - resultContainer.firstTable(), - resultContainer.secondTable(), - resultContainer.difference()); - } catch (IOException e) { - throw new RuntimeException(e); + private void writeToCsvFile(ResultContainer resultContainer) { + try (Writer writer = new FileWriter(resultDirectory.resolve("result-" + Instant.now().getEpochSecond() + ".csv").toFile())) { + CustomMappingStrategy mappingStrategy = new CustomMappingStrategy<>(); + mappingStrategy.setType(ResultContainer.class); + + StatefulBeanToCsv sbc = new StatefulBeanToCsvBuilder(writer) + .withMappingStrategy(mappingStrategy) + .build(); + sbc.write(resultContainer); + } catch (CsvRequiredFieldEmptyException | CsvDataTypeMismatchException | IOException e) { + log.error(e.getMessage(), e); } } } diff --git a/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/util/CustomMappingStrategy.java b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/util/CustomMappingStrategy.java new file mode 100644 index 000000000..5d796c896 --- /dev/null +++ b/clearing-parent/control-service/src/main/java/ru/spcex/clearing/control/util/CustomMappingStrategy.java @@ -0,0 +1,52 @@ +package ru.spcex.clearing.control.util; + +import com.opencsv.bean.AbstractCsvConverter; +import com.opencsv.bean.BeanField; +import com.opencsv.bean.ColumnPositionMappingStrategy; +import com.opencsv.bean.ConverterNumber; +import com.opencsv.bean.CsvBindByName; +import com.opencsv.bean.CsvBindByPosition; +import com.opencsv.bean.CsvConverter; +import com.opencsv.bean.CsvNumber; +import com.opencsv.exceptions.CsvBadConverterException; +import com.opencsv.exceptions.CsvChainedException; +import com.opencsv.exceptions.CsvFieldAssignmentException; +import com.opencsv.exceptions.CsvRequiredFieldEmptyException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.Locale; + +public class CustomMappingStrategy extends ColumnPositionMappingStrategy { + public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException { + super.generateHeader(bean); + String[] headers = new String[this.getFieldMap().values().size()]; + for (BeanField field : this.getFieldMap().values()) { + CsvBindByPosition positionAnnotation = field.getField().getAnnotation(CsvBindByPosition.class); + CsvBindByName nameAnnotation = field.getField().getAnnotation(CsvBindByName.class); + headers[positionAnnotation.position()] = nameAnnotation.column(); + } + return headers; + } + + @Override + public String[] transmuteBean(T bean) throws CsvFieldAssignmentException, CsvChainedException { + return super.transmuteBean(bean); + } + + @Override + protected CsvConverter determineConverter(Field field, Class elementType, String locale, String writeLocale, Class customConverter) throws CsvBadConverterException { + CsvConverter csvConverter = super.determineConverter(field, elementType, locale, writeLocale, customConverter); + if (csvConverter instanceof ConverterNumber && field.getType() == BigDecimal.class) { + CsvNumber csvNumberAnnotation = field.getAnnotation(CsvNumber.class); + String formatValue = csvNumberAnnotation.value(); + String writeFormat = csvNumberAnnotation.writeFormat(); + csvConverter = new ConverterNumber(BigDecimal.class, + Locale.getDefault().toString(), + Locale.getDefault().toString(), + Locale.getDefault(), + formatValue, + writeFormat); + } + return csvConverter; + } +} diff --git a/clearing-parent/control-service/src/main/resources/application.properties b/clearing-parent/control-service/src/main/resources/application.properties index 7e62ee8b0..50e6b5083 100644 --- a/clearing-parent/control-service/src/main/resources/application.properties +++ b/clearing-parent/control-service/src/main/resources/application.properties @@ -1,7 +1,27 @@ +# Result directory +control-service.result-directory=C:\\Users\\inikolaev\\Desktop\\result + # Database settings control-service.database.username=clearing control-service.database.password=Aa111111 control-service.database.url=jdbc:postgresql://localhost:5433/clearing -# Result directory -control-service.result-directory=C:\\Users\\ivan\\Desktop\\result +# Hazelcast settings +control-service.hazelcast.cluster-members=10.200.200.181:5701 +control-service.hazelcast.login=dev +control-service.hazelcast.password=dev-pass + +# Kafka consumer settings +control-service.kafka-consumer.bootstrap-servers=localhost:19092 +control-service.kafka-consumer.group-id=dev-group-balance-service +control-service.kafka-consumer.enable-auto-commit=false +control-service.kafka-consumer.session-timeout-ms=30000 +control-service.kafka-consumer.auto-offset-reset=latest + +# Kafka producer settings +control-service.kafka-producer.bootstrap-servers=localhost:19092 +control-service.kafka-producer.acks=all +control-service.kafka-producer.retries=0 +control-service.kafka-producer.batch-size=16384 +control-service.kafka-producer.linger-ms=1 +control-service.kafka-producer.buffer-memory=33554432 diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Task.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Task.java index 50988fa24..954b168c2 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Task.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Task.java @@ -59,6 +59,9 @@ public enum Task implements IEnumKey { finishBadSessions("CCLR"),//Завершение неудачных клиринговых сессий sendLim_LIMC("LIMC"), // Выгрузка в Торговую систему остатков по валюте (отправка lim)" makeFiles_MTCR("MTCR"), // Формирование файлов с МТКР + generalControl_GLCL("GLCL"), + internalControl_ILCL("ILCL"), + internalFinlControl_IFCL("IFCL"), ; private final String key; diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/ControlServiceRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/ControlServiceRequest.java new file mode 100644 index 000000000..8a78f463d --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/clearing/ControlServiceRequest.java @@ -0,0 +1,27 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.clearing; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ru.spcex.platform.enumeration.SessionType; + +public class ControlServiceRequest { + @JsonProperty + private Long sessionId; + @JsonProperty + private SessionType sessionType; + + public Long getSessionId() { + return sessionId; + } + + public void setSessionId(Long sessionId) { + this.sessionId = sessionId; + } + + public SessionType getSessionType() { + return sessionType; + } + + public void setSessionType(SessionType sessionType) { + this.sessionType = sessionType; + } +}