Compare commits

..

7 commits

Author SHA1 Message Date
etreschenkov
3e7bfa51a0 http://jira.mfd.msk:8088/browse/BKI-3341 2025-12-03 15:47:58 +03:00
etreschenkov
99222c740e http://jira.mfd.msk:8088/browse/BKI-3288 2025-11-06 12:20:58 +03:00
etreschenkov
5dc5e72496 http://jira.mfd.msk:8088/browse/BKI-3288 2025-11-06 12:20:38 +03:00
etreschenkov
b85cda38ef http://jira.mfd.msk:8088/browse/BKI-3287 2025-11-05 17:31:21 +03:00
etreschenkov
14138b4455 http://jira.mfd.msk:8088/browse/BKI-3287 2025-11-05 17:14:46 +03:00
etreschenkov
786cc89718 http://jira.mfd.msk:8088/browse/BKI-3286 2025-11-05 12:36:38 +03:00
etreschenkov
6b0f8668ae http://jira.mfd.msk:8088/browse/BKI-3286 2025-11-05 12:29:12 +03:00
98 changed files with 847 additions and 4892 deletions

View file

@ -8,9 +8,9 @@ server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b
# datasources # datasources
# indic # indic
ct.indic-data-source-settings.driver=com.ibm.db2.jcc.DB2Driver ct.indic-data-source-settings.driver=com.ibm.db2.jcc.DB2Driver
ct.indic-data-source-settings.url=${DB_URL_INDIC:jdbc:db2://10.230.227.100:2668/cprosd22:currentSchema=INDIC;} ct.indic-data-source-settings.url=${DB_URL_INDIC:jdbc:db2://10.230.227.101:2668/cprosd22:currentSchema=INDIC;}
ct.indic-data-source-settings.username=${USERNAME_INDIC:ros9appl} ct.indic-data-source-settings.username=${USERNAME_INDIC:tester}
ct.indic-data-source-settings.password=${PASSWORD_INDIC:R0s9Appl} ct.indic-data-source-settings.password=${PASSWORD_INDIC:tester}
ct.indic-data-source-settings.maximum-pool-size=${MAXIMUM_POOL_SIZE_INDIC:10} ct.indic-data-source-settings.maximum-pool-size=${MAXIMUM_POOL_SIZE_INDIC:10}
ct.indic-data-source-settings.minimum-idle=${MINIMUM_IDLE_INDIC:10} ct.indic-data-source-settings.minimum-idle=${MINIMUM_IDLE_INDIC:10}
ct.indic-data-source-settings.connection-timeout=${CONNECTION_TIMEOUT_INDIC:10000} ct.indic-data-source-settings.connection-timeout=${CONNECTION_TIMEOUT_INDIC:10000}
@ -60,18 +60,13 @@ ct.signals-root-uri=${SIGNALS_ROOT_URI:http://10.230.227.102:11094/signalRUalfa/
ct.signals-token=${SIGNALS_TOKEN:gttcHxwcGojoAvZK} ct.signals-token=${SIGNALS_TOKEN:gttcHxwcGojoAvZK}
ct.signals-user=${SIGNALS_USER:0001ZZ000001} ct.signals-user=${SIGNALS_USER:0001ZZ000001}
#ct.load-dir=/opt/app/reports ct.load-dir=/opt/app/reports
ct.load-dir=D:\\folder_name\\signals #ct.load-dir=D:\\folder_name\\signals
# min # min
ct.monitoring-interval=${MONITORING_INTERVAL:15} ct.monitoring-interval=${MONITORING_INTERVAL:15}
# days # days
ct.update-flags-interval=${UPDATE_FLAGS_INTERVAL:1} ct.update-flags-cron-expression=${UPDATE_FLAGS_CRON_EXPRESSION:0 0 22 * * *}
ct.days-to-update-flags=${DAYS_TO_UPDATE_FLAGS:30} ct.days-to-update-flags=${DAYS_TO_UPDATE_FLAGS:30}
ct.encoding=windows-1251 ct.encoding=windows-1251
#Batch size and global pipeline timeout
ct.batchSize=1000
#in minute
ct.timeout=30

View file

@ -1,5 +1,5 @@
#commons #commons
APP_VERSION=1.20 APP_VERSION=1.24
REGISTRY_URL=rh8-vm3.nbki.msk:5000 REGISTRY_URL=rh8-vm3.nbki.msk:5000
JMX_PORT= JMX_PORT=
JMX_HOSTNAME= JMX_HOSTNAME=
@ -71,7 +71,7 @@ SIGNALS_USER=0001ZZ000001
# min # min
MONITORING_INTERVAL=15 MONITORING_INTERVAL=15
# days # days
UPDATE_FLAGS_INTERVAL=1 UPDATE_FLAGS_CRON_EXPRESSION=0 22 * * *
DAYS_TO_UPDATE_FLAGS=30 DAYS_TO_UPDATE_FLAGS=30

View file

@ -10,12 +10,12 @@
</parent> </parent>
<groupId>ru.nbch</groupId> <groupId>ru.nbch</groupId>
<artifactId>credit-tracker</artifactId> <artifactId>credit-tracker</artifactId>
<version>2.0</version> <version>1.24</version>
<name>credit_tracker</name> <name>credit_tracker</name>
<description>Credit Tracker Application</description> <description>Credit Tracker Application</description>
<properties> <properties>
<java.version>21</java.version> <java.version>17</java.version>
<git.info.prefix>git_info</git.info.prefix> <git.info.prefix>git_info</git.info.prefix>
<git.info.filename>git.properties</git.info.filename> <git.info.filename>git.properties</git.info.filename>
<registry.url>10.230.238.56:5000</registry.url> <registry.url>10.230.238.56:5000</registry.url>
@ -41,10 +41,6 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.ibm.db2</groupId> <groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId> <artifactId>jcc</artifactId>

View file

@ -3,18 +3,21 @@ package ru.nbch.credit_tracker;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import ru.nbch.credit_tracker.service.MonitoringService;
import ru.nbch.credit_tracker.service.UpdateProcessingPackagesService;
import ru.nbch.credit_tracker.service.UpdatingFlagsService;
@SpringBootApplication @SpringBootApplication
public class CreditTrackerApplication { public class CreditTrackerApplication {
public static void main(String[] args) { public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(CreditTrackerApplication.class, args); ConfigurableApplicationContext ctx = SpringApplication.run(CreditTrackerApplication.class, args);
// UpdateProcessingPackagesService updateProcessingPackagesService = ctx.getBean(UpdateProcessingPackagesService.class); UpdateProcessingPackagesService updateProcessingPackagesService = ctx.getBean(UpdateProcessingPackagesService.class);
// MonitoringService monitoringService = ctx.getBean(MonitoringService.class); MonitoringService monitoringService = ctx.getBean(MonitoringService.class);
// UpdatingFlagsService updatingFlagsService = ctx.getBean(UpdatingFlagsService.class); UpdatingFlagsService updatingFlagsService = ctx.getBean(UpdatingFlagsService.class);
// updateProcessingPackagesService.run(); monitoringService.run();
// monitoringService.run(); updatingFlagsService.run();
// updatingFlagsService.run(); updateProcessingPackagesService.run();
} }
} }

View file

@ -1,122 +0,0 @@
package ru.nbch.credit_tracker.builder;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import ru.nbch.credit_tracker.entities.external_request.signals.Person;
import ru.nbch.credit_tracker.model.IdData;
import ru.nbch.credit_tracker.model.NameData;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.utils.CTUtil;
@Slf4j
public class PersonFactoryBuilderV2 {
private final static DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private final static String DEFAULT_DATE = "01.01.2000";
private final static String IP = "0";
private final static String OKSM = "999";
private final static String CONSENT_PURPOSE = "16";
private final static String REPORT_USER = "АО «НБКИ»";
private final static String LIABILITY = "1";
private final static String CONSENT_PERIOD = "3";
private final static String REPORT_USER_REG_NUM = "1057746710713";
private final static String REPORT_USER_TAX_ID = "7703548386";
private final static String CONSENT_HASH = "hash";
private final static String CONSENT_DIV_CODE = "000-000";
private final static String INQ_PURPOSE = "16";
private final static String ODT = "Иной документ";
private final static String CONSENT_ISSUE_AUTHORITY = "Кем выдан";
private SubjectProfile personData;
private Long uid;
public static PersonFactoryBuilderV2 builder() {
return new PersonFactoryBuilderV2();
}
public PersonFactoryBuilderV2 personData(SubjectProfile personData) {
this.personData = personData;
//todo откуда брать этот id
// this.uid = personData.getIdentifier();
return this;
}
public Person build() {
if (personData == null) {
return null;
}
NameData nameData = personData.getNameData();
IdData passportData = personData.getIdData();
Person person = new Person();
person.setS(nameData.getLastName());
person.setN(nameData.getFirstName());
person.setM(StringUtils.isNotBlank(nameData.getMiddleName()) ? nameData.getMiddleName() : null);
person.setD(DATE_FORMAT.format(nameData.getBirthDate()));
person.setPn(getPn(passportData.getSerNum(), passportData.getIdNum()));
person.setPd(DATE_FORMAT.format(LocalDate.now())); // Do not use passport iss_date. Signal service could reject the package
person.setDt(getDt(passportData.getIdType(), passportData.getIdType2()));
if (person.getDt() != null && person.getDt().equals("999")) {
person.setOdt(ODT);
person.setConsentOdt(ODT);
}
person.setIp(IP);
person.setUid(uid);
person.setOksm(OKSM);
person.setConsentDate(DATE_FORMAT.format(LocalDate.now()));
person.setConsentPurpose(CONSENT_PURPOSE);
person.setReportUser(REPORT_USER);
person.setLiability(LIABILITY);
person.setConsentPeriod(CONSENT_PERIOD);
person.setAgrDate(DATE_FORMAT.format(LocalDate.now()));
person.setReportUserRegNum(REPORT_USER_REG_NUM);
person.setReportUserTaxID(REPORT_USER_TAX_ID);
person.setConsentHash(CONSENT_HASH);
person.setCs(person.getS());
person.setConsentN(person.getN());
person.setConsentM(person.getM());
person.setConsentBd(person.getD());
person.setConsentPn(person.getPn());
person.setConsentPd(person.getPd());
person.setConsentDivCode(CONSENT_DIV_CODE);
person.setConsentDt(person.getDt());
person.setConsentIssueAuthority(CONSENT_ISSUE_AUTHORITY);
person.setInqPurpose(INQ_PURPOSE);
return person;
}
private String getPn(String serNum, String idNum) {
String normSerNum = CTUtil.normalize(serNum);
String normIdNum = CTUtil.normalize(idNum);
return StringUtils.isNotBlank(normSerNum) ? normSerNum + normIdNum : normIdNum;
}
private String getDt(String idType, String idType2) {
if (StringUtils.isNotBlank(idType2)) {
return idType2;
} else {
return switch (idType) {
case "21" -> "21";
case "22" -> "22.1";
case "09" -> "22.2";
case "26" -> "23";
case "04" -> "24";
case "07" -> "25";
case "14", "03" -> "27";
case "10" -> "31";
case "15" -> "32";
case "13" -> "37";
case "06", "27" -> "28";
case "01", "02" -> "999";
default -> {
log.warn("ID_TYPE2 is blank and no matching found for ID_TYPE {}", idType);
yield "";
}
};
}
}
}

View file

@ -1,95 +0,0 @@
package ru.nbch.credit_tracker.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.enums.SignalError;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
import ru.nbch.credit_tracker.exceptions.SignalException;
import ru.nbch.credit_tracker.model.MonitoringValidationError;
import ru.nbch.credit_tracker.model.ValidationResult;
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
import ru.nbch.credit_tracker.utils.ValidationService;
import ru.nbch.credit_tracker.utils.ZipUtils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.clientExcp;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.serverExcp;
@Component
@Slf4j
public class MonitoringRequestValidator {
private final CreditTrackerSettings config;
private final ValidationService validationService;
private final StaxXmlProcessor staxXmlProcessor;
public MonitoringRequestValidator(CreditTrackerSettings config,
ValidationService validationService,
StaxXmlProcessor staxXmlProcessor) {
this.config = config;
this.validationService = validationService;
this.staxXmlProcessor = staxXmlProcessor;
}
public void validateZipContent(MultipartFile file) {
log.trace("Validating zip content");
if (file.isEmpty() || !ZipUtils.isZipFile(file)) {
throw clientExcp(SignalError.ERROR_001, XmlErrorFormat.MonitoringError);
}
// Валидация Архива на количетсво файлов, размер и расширение
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
Optional<ZipEntry> xmlFileEntry = ZipUtils.extractValidEntry(zipInputStream);
if (xmlFileEntry.isEmpty()) {
throw clientExcp(SignalError.ERROR_001, XmlErrorFormat.MonitoringError);
}
long maxXmlFileSize = config.getMaxXmlFileSize() * 1024 * 1024;
if (xmlFileEntry.get().getSize() > maxXmlFileSize) {
throw clientExcp(SignalError.ERROR_009, XmlErrorFormat.MonitoringError);
}
} catch (IOException e) {
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
log.trace("Validated zip content ok!");
}
public ValidationResult readAndValidateMonitoringRequest(String filePath) {
log.debug("Validating monitoring request...");
ValidationResult validationResult = new ValidationResult();
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Path.of(filePath)), 8 * 1024 * 1024)) {
staxXmlProcessor.read(in, MonitoringRequest.class,
request -> {
try {
validationService.validateMonitoringRequest(request);
if (request.getPackageName() != null && validationResult.getPackageName() == null) {
validationResult.setPackageName(request.getPackageName());
}
if (request.getUserId() != null && validationResult.getUserId() == null) {
validationResult.setUserId(request.getUserId());
}
if (request.getPassword() != null && validationResult.getPassword() == null) {
validationResult.setPassword(request.getPassword());
}
request.getSubjects().getSubject().clear();
} catch (SignalException e) {
validationResult.addError(new MonitoringValidationError(-1, "", e));
}
},
request -> request.getSubjects().getSubject().isEmpty(),
40000, "/MonitoringRequest/Subjects/Subject");
} catch (IOException e) {
throw new RuntimeException("Error while parsing and validating monitoring request", e);
}
log.debug("Validating monitoring request {}!", validationResult.getValidationErrors().isEmpty() ? "ok" : "fail");
return validationResult;
}
}

View file

@ -1,28 +0,0 @@
package ru.nbch.credit_tracker.component;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component
public class RepositoryTimingAspect {
@Around("within(@org.springframework.stereotype.Repository *)")
public Object timeMapper(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed();
} finally {
long tookMs = (System.nanoTime() - start) / 1_000_000;
String method = pjp.getSignature().toShortString();
if (tookMs > 1000) {
log.warn("Slow jdbc template call {} took {} ms", method, tookMs);
} else {
log.debug("Jdbc template call {} took {} ms", method, tookMs);
}
}
}
}

View file

@ -4,8 +4,6 @@ import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceBuilder;
import ru.nbch.credit_tracker.config.settings.DataSourceSettings; import ru.nbch.credit_tracker.config.settings.DataSourceSettings;
import javax.sql.DataSource;
public class DatabaseConfig { public class DatabaseConfig {
protected HikariDataSource createDataSource(DataSourceSettings settings) { protected HikariDataSource createDataSource(DataSourceSettings settings) {

View file

@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.config.settings.DataSourceSettings; import ru.nbch.credit_tracker.config.settings.DataSourceSettings;
@ -61,10 +60,5 @@ public class IndicDataSourceConfig extends DatabaseConfig {
sqlSessionFactoryBean.setTypeHandlersPackage("ru.nbch.credit_tracker.config.datasource.handlers"); sqlSessionFactoryBean.setTypeHandlersPackage("ru.nbch.credit_tracker.config.datasource.handlers");
return sqlSessionFactoryBean.getObject(); return sqlSessionFactoryBean.getObject();
} }
@Bean(name = "jdbcTemplateIndic")
public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(@Qualifier("indicDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
} }

View file

@ -1,53 +0,0 @@
package ru.nbch.credit_tracker.config.datasource.handlers;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.UUID;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@MappedTypes({UUID.class})
public class UUIDTypeHandler implements TypeHandler<UUID> {
private static final Logger log = LoggerFactory.getLogger(UUIDTypeHandler.class);
@Override
public void setParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setObject(i, null, Types.OTHER);
} else {
ps.setObject(i, parameter.toString(), Types.OTHER);
}
}
@Override
public UUID getResult(ResultSet rs, String columnName) throws SQLException {
return toUUID(rs.getString(columnName));
}
@Override
public UUID getResult(ResultSet rs, int columnIndex) throws SQLException {
return toUUID(rs.getString(columnIndex));
}
@Override
public UUID getResult(CallableStatement cs, int columnIndex) throws SQLException {
return toUUID(cs.getString(columnIndex));
}
private static UUID toUUID(String val) {
if (val != null && !val.isEmpty() && !val.isBlank()) {
try {
return UUID.fromString(val);
} catch (IllegalArgumentException e) {
log.warn("Bad UUID found: {}", val);
}
}
return null;
}
}

View file

@ -1,14 +0,0 @@
package ru.nbch.credit_tracker.config.mdc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ru.nbch.credit_tracker.utils.LogUtil;
// Annotation to only clear the MDC key at method exit
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClearMdc {
String key() default LogUtil.mdcKey;
}

View file

@ -1,27 +1,18 @@
package ru.nbch.credit_tracker.config.mdc; package ru.nbch.credit_tracker.config.mdc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import ru.nbch.credit_tracker.entities.app.UserPackage; import org.slf4j.MDC;
import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest; import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.utils.LogUtil;
@Slf4j @Slf4j
@Aspect @Aspect
public class MdcAspect { public class MdcAspect {
// For methods that only require clearing the MDC at the end
@Around("@annotation(clearMdc)")
public Object clearMdcAdvice(ProceedingJoinPoint pjp, ClearMdc clearMdc) throws Throwable {
try {
return pjp.proceed();
} finally {
LogUtil.removeMdcLogKey(clearMdc.key());
}
}
// For methods that set an MDC value (from a method argument) and then clear it at the end // For methods that set an MDC value (from a method argument) and then clear it at the end
@Around("@annotation(setAndClearMdc)") @Around("@annotation(setAndClearMdc)")
public Object setAndClearMdcAdvice(ProceedingJoinPoint pjp, SetAndClearMdc setAndClearMdc) throws Throwable { public Object setAndClearMdcAdvice(ProceedingJoinPoint pjp, SetAndClearMdc setAndClearMdc) throws Throwable {
@ -29,18 +20,30 @@ public class MdcAspect {
int index = setAndClearMdc.argIndex(); int index = setAndClearMdc.argIndex();
Object obj = (args != null && args.length > index) ? args[index] : null; Object obj = (args != null && args.length > index) ? args[index] : null;
if (obj instanceof MonitoringRequest request) { if (obj instanceof MonitoringRequest request) {
LogUtil.addMdcLogKey(setAndClearMdc.key(), request.getPackageName()); if (request.getPackageName() != null) {
putMdcValue(setAndClearMdc.key(), request.getPackageName());
}
} else if (obj instanceof UserPackage userPackage) { } else if (obj instanceof UserPackage userPackage) {
LogUtil.addMdcLogKey(setAndClearMdc.key(), userPackage.getPackageName()); if (userPackage.getPackageName() != null) {
putMdcValue(setAndClearMdc.key(), userPackage.getPackageName());
}
} else if (obj instanceof String packageName) { } else if (obj instanceof String packageName) {
LogUtil.addMdcLogKey(setAndClearMdc.key(), packageName); putMdcValue(setAndClearMdc.key(), packageName);
} else { } else {
log.debug("no packageName found for for {}", pjp.toShortString()); log.debug("no packageName found for for {}", pjp.toShortString());
} }
try { try {
return pjp.proceed(); return pjp.proceed();
} finally { } finally {
LogUtil.removeMdcLogKey(setAndClearMdc.key()); MDC.remove(setAndClearMdc.key());
}
}
private void putMdcValue(String key, String value) {
try {
MDC.put(key, "[" + value + "] ");
} catch (Throwable e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
} }
} }
} }

View file

@ -4,13 +4,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import ru.nbch.credit_tracker.utils.LogUtil;
// Annotation to set an MDC key based on a method argument and clear it after method execution // Annotation to set an MDC key based on a method argument and clear it after method execution
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface SetAndClearMdc { public @interface SetAndClearMdc {
String key() default LogUtil.mdcKey; String key() default "ru.nbch.credit_tracker.log.mdc.key";
// The index of the method argument to use for the MDC value (defaults to the first argument) // The index of the method argument to use for the MDC value (defaults to the first argument)
int argIndex() default 0; int argIndex() default 0;
} }

View file

@ -1,6 +1,5 @@
package ru.nbch.credit_tracker.config.rest; package ru.nbch.credit_tracker.config.rest;
import java.nio.charset.Charset;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -10,19 +9,15 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.user_response.errors.CommonError; import ru.nbch.credit_tracker.entities.user_response.errors.*;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringActivePackagesError;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringAllPackagesError;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringDownloadError;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringError;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringPackageDeleteError;
import ru.nbch.credit_tracker.entities.user_response.errors.MonitoringReportListError;
import ru.nbch.credit_tracker.enums.SignalError; import ru.nbch.credit_tracker.enums.SignalError;
import ru.nbch.credit_tracker.exceptions.SignalClientException; import ru.nbch.credit_tracker.exceptions.SignalClientException;
import ru.nbch.credit_tracker.exceptions.SignalException; import ru.nbch.credit_tracker.exceptions.SignalException;
import ru.nbch.credit_tracker.exceptions.SignalServerException; import ru.nbch.credit_tracker.exceptions.SignalServerException;
import ru.nbch.credit_tracker.utils.error.SimpleMessageResolver; import ru.nbch.credit_tracker.utils.error.SimpleMessageResolver;
import java.nio.charset.Charset;
@Slf4j @Slf4j
@RestControllerAdvice @RestControllerAdvice
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
@ -47,33 +42,34 @@ public class GlobalExceptionHandler {
return new ResponseEntity<>(error, headers, HttpStatus.BAD_REQUEST); return new ResponseEntity<>(error, headers, HttpStatus.BAD_REQUEST);
} }
@ExceptionHandler(value = SignalClientException.class, produces = MediaType.APPLICATION_XML_VALUE) @ExceptionHandler(value = SignalException.class, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<CommonError> handleClientException(SignalClientException ex) { public ResponseEntity<CommonError> handleClientException(SignalException ex) {
CommonError err = getCommonError(ex);
String packageName = ex.getPackageName();
if(packageName != null ) {
log.warn("Response error: {}. PackageName: {}", err.getErrorText(), packageName);
} else {
log.warn("Response error: {}", err.getErrorText());
}
return new ResponseEntity<>(err, xmlHeaders(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = SignalServerException.class, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<CommonError> handleServerException(SignalServerException ex) {
CommonError commonError = getCommonError(ex);
Exception cause = ex.getCause();
log.error(cause.getMessage(), cause);
return new ResponseEntity<>(commonError, xmlHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
private HttpHeaders xmlHeaders() {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "xml", Charset.forName(encoding))); headers.setContentType(new MediaType("application", "xml", Charset.forName(encoding)));
return headers;
String errorText = messageResolver.resolve(ex.getErrorMessage());
CommonError commonError = getCommonError(ex);
commonError.setStatus("error");
commonError.setErrorCode(ex.getErrorMessage().getSubject().getCode());
commonError.setErrorText(errorText);
if (ex instanceof SignalClientException) {
String packageName = ((SignalClientException) ex).getPackageName();
if(packageName != null ) {
log.warn("Response error: {}. PackageName: {}", errorText, packageName);
} else {
log.warn("Response error: {}", errorText);
}
return new ResponseEntity<>(commonError, headers, HttpStatus.BAD_REQUEST);
} else {
Exception cause = ((SignalServerException) ex).getCause();
log.error(cause.getMessage(), cause);
return new ResponseEntity<>(commonError, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
} }
private CommonError getCommonError(SignalException ex) { private static CommonError getCommonError(SignalException ex) {
CommonError commonError; CommonError commonError;
switch (ex.getErrorFormat()) { switch (ex.getErrorFormat()) {
case MonitoringError -> commonError = new MonitoringError(); case MonitoringError -> commonError = new MonitoringError();
@ -84,10 +80,6 @@ public class GlobalExceptionHandler {
case MonitoringPackageDeleteError -> commonError = new MonitoringPackageDeleteError(); case MonitoringPackageDeleteError -> commonError = new MonitoringPackageDeleteError();
default -> throw new IllegalStateException("Unsupported error format: " + ex.getErrorFormat()); default -> throw new IllegalStateException("Unsupported error format: " + ex.getErrorFormat());
} }
commonError.setStatus("error");
commonError.setErrorCode(ex.getErrorMessage().getSubject().getCode());
String errorText = messageResolver.resolve(ex.getErrorMessage());
commonError.setErrorText(errorText);
return commonError; return commonError;
} }
} }

View file

@ -29,11 +29,8 @@ public class CreditTrackerSettings {
private Long maxXmlFileSize; private Long maxXmlFileSize;
private String loadDir; private String loadDir;
private Long monitoringInterval; private Long monitoringInterval;
private Long updateFlagsInterval; private String updateFlagsCronExpression;
private Long daysToUpdateFlags; private Long daysToUpdateFlags;
private String encoding; private String encoding;
private Integer batchSize;
private Integer timeout;
} }

View file

@ -1,36 +0,0 @@
package ru.nbch.credit_tracker.config.task;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.model.impl.PartFilePayload;
import ru.nbch.credit_tracker.model.impl.SignalXmlFilePayload;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.indic.FlagsService2;
import ru.nbch.credit_tracker.service.task.impl.FlagsEnricher;
import ru.nbch.credit_tracker.service.task.impl.KickPipeLineImpl;
import ru.nbch.credit_tracker.service.task.impl.XmlMergeCoordinator;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineStarter;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineTerminator;
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
@Configuration
public class PipelineStarterConfig {
@Bean
public BiFunction<Long, String, IPipeLineStarter<SubjectPayload>> pipelineStarterFactory(StaxXmlProcessor xml, CreditTrackerSettings creditTrackerSettings) {
return (packageId, packageName) -> new KickPipeLineImpl(xml, packageId, packageName, creditTrackerSettings);
}
@Bean
public Supplier<IPipeLineTerminator<PartFilePayload, SignalXmlFilePayload>> pipelineTerminatorFactory(CreditTrackerSettings creditTrackerSettings) {
return () -> new XmlMergeCoordinator(creditTrackerSettings);
}
@Bean
public Supplier<FlagsEnricher> flagEnricherFactory(FlagsService2 flagsService, PhoneFidMapMapper mapper) {
return () -> new FlagsEnricher(flagsService, mapper);
}
}

View file

@ -1,26 +0,0 @@
package ru.nbch.credit_tracker.config.task;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.nbch.credit_tracker.model.impl.PartFilePayload;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
@Configuration
public class StageConfig {
@Bean("saveSubjectQueueFactory")
public StageQueueFactory<SubjectPayload> saveSubjectQueueFactory() {
return new StageQueueFactory<>(5);
}
@Bean("idEnricherQueueFactory")
public StageQueueFactory<SubjectPayload> idEnricherQueueFactory() {
return new StageQueueFactory<>();
}
@Bean("partFileQueueFactory")
public StageQueueFactory<PartFilePayload> partFileQueueFactory() {
return new StageQueueFactory<>(10000);
}
}

View file

@ -1,33 +0,0 @@
package ru.nbch.credit_tracker.config.task;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.beans.factory.FactoryBean;
import ru.nbch.credit_tracker.model.StagePayload;
public class StageQueueFactory<T extends StagePayload> implements FactoryBean<BlockingQueue<T>> {
private final int capacity;
public StageQueueFactory(int capacity) {
this.capacity = capacity;
}
public StageQueueFactory() {
this(10);
}
@Override
public BlockingQueue<T> getObject() throws Exception {
return new LinkedBlockingQueue<>(capacity);
}
@Override
public Class<?> getObjectType() {
return BlockingQueue.class;
}
@Override
public boolean isSingleton() {
return false;
}
}

View file

@ -3,14 +3,8 @@ package ru.nbch.credit_tracker.controller;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import ru.nbch.credit_tracker.config.mdc.ClearMdc;
import ru.nbch.credit_tracker.entities.user_request.active_packages.MonitoringActivePackagesRequest; import ru.nbch.credit_tracker.entities.user_request.active_packages.MonitoringActivePackagesRequest;
import ru.nbch.credit_tracker.entities.user_request.all_packages.MonitoringAllPackagesRequest; import ru.nbch.credit_tracker.entities.user_request.all_packages.MonitoringAllPackagesRequest;
import ru.nbch.credit_tracker.entities.user_response.active_packages.MonitoringActivePackagesResponse; import ru.nbch.credit_tracker.entities.user_response.active_packages.MonitoringActivePackagesResponse;
@ -18,70 +12,47 @@ import ru.nbch.credit_tracker.entities.user_response.all_packages.MonitoringAllP
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringPackageDeleteResponse; import ru.nbch.credit_tracker.entities.user_response.success.MonitoringPackageDeleteResponse;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringReportListResponse; import ru.nbch.credit_tracker.entities.user_response.success.MonitoringReportListResponse;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringSetupResponse; import ru.nbch.credit_tracker.entities.user_response.success.MonitoringSetupResponse;
import ru.nbch.credit_tracker.enums.SignalError; import ru.nbch.credit_tracker.facade.SignalFacade;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
import ru.nbch.credit_tracker.exceptions.SignalException;
import ru.nbch.credit_tracker.facade.SignalFacadeV3;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.serverExcp;
@RestController @RestController
@RequestMapping("/api/v1/signal") @RequestMapping("/api/v1/signal")
public class SignalControllerV3 { public class SignalController {
private final SignalFacadeV3 signalFacadeV3; private final SignalFacade signalFacade;
public SignalControllerV3(SignalFacadeV3 signalFacadeV3) { public SignalController(SignalFacade signalFacade) {
this.signalFacadeV3 = signalFacadeV3; this.signalFacade = signalFacade;
} }
@PostMapping(value = "/set", produces = MediaType.APPLICATION_XML_VALUE) @PostMapping(value = "/set", produces = MediaType.APPLICATION_XML_VALUE)
@ClearMdc
public ResponseEntity<MonitoringSetupResponse> setupMonitoring(@RequestParam("file") MultipartFile file) { public ResponseEntity<MonitoringSetupResponse> setupMonitoring(@RequestParam("file") MultipartFile file) {
signalFacadeV3.setupMonitoringRequest(file); return signalFacade.setupMonitoring(file);
return ResponseEntity.ok().body(new MonitoringSetupResponse());
} }
@PostMapping(value = "/active_packages", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) @PostMapping(value = "/active_packages", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MonitoringActivePackagesResponse> findActivePackages(@RequestBody MonitoringActivePackagesRequest activePackagesRequest) { public ResponseEntity<MonitoringActivePackagesResponse> findActivePackages(@RequestBody MonitoringActivePackagesRequest activePackagesRequest) {
try { return signalFacade.findActivePackages(activePackagesRequest);
return signalFacadeV3.findActivePackages(activePackagesRequest);
} catch (Exception e) {
throwOrWrap(SignalError.ERROR_013, XmlErrorFormat.MonitoringActivePackagesError, e);
return null;
}
} }
@PostMapping(value = "/all_packages", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) @PostMapping(value = "/all_packages", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MonitoringAllPackagesResponse> findAllPackages(@RequestBody MonitoringAllPackagesRequest allPackagesRequest) { public ResponseEntity<MonitoringAllPackagesResponse> findAllPackages(@RequestBody MonitoringAllPackagesRequest allPackagesRequest) {
try { return signalFacade.findAllPackages(allPackagesRequest);
return signalFacadeV3.findAllPackages(allPackagesRequest);
} catch (Exception e) {
throwOrWrap(SignalError.ERROR_013, XmlErrorFormat.MonitoringAllPackagesError, e);
return null;
} }
}
@PostMapping(value = "/delete", params = {"username", "packageName"}, produces = MediaType.APPLICATION_XML_VALUE) @PostMapping(value = "/delete", params = {"username", "packageName"}, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MonitoringPackageDeleteResponse> deletePackage(@RequestParam("username") String username, @RequestParam("packageName") String packageName) { public ResponseEntity<MonitoringPackageDeleteResponse> deletePackage(@RequestParam("username") String username, @RequestParam("packageName") String packageName) {
return signalFacadeV3.deletePackage(username, packageName); return signalFacade.deletePackage(username, packageName);
} }
@GetMapping(value = "/report_list", params = {"username", "packageName"}, produces = MediaType.APPLICATION_XML_VALUE) @GetMapping(value = "/report_list", params = {"username", "packageName"}, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MonitoringReportListResponse> getReportList(@RequestParam("username") String username, @RequestParam("packageName") String packageName) { public ResponseEntity<MonitoringReportListResponse> getReportList(@RequestParam("username") String username, @RequestParam("packageName") String packageName) {
return signalFacadeV3.getReportList(username, packageName); return signalFacade.getReportList(username, packageName);
} }
@GetMapping(value = "/download", params = "file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @GetMapping(value = "/download", params = "file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> downloadFile(@RequestParam("file") String file) { public ResponseEntity<Resource> downloadFile(@RequestParam("file") String file) {
return signalFacadeV3.downloadFile(file); return signalFacade.downloadFile(file);
} }
private static void throwOrWrap(SignalError error, XmlErrorFormat specific, Exception ex) {
if (ex instanceof SignalException) {
throw (SignalException) ex;
}
throw serverExcp(error, specific, ex);
}
} }

View file

@ -1,41 +0,0 @@
package ru.nbch.credit_tracker.entities.app;
import java.util.UUID;
import lombok.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PhoneFidMap {
private Long id;
private Long packageId;
private UUID recUuid;
private String subjectId;
private String phone;
private Integer fid;
private Integer flagDebt5000;
private Integer flag90days;
private LocalDateTime calculatedAt;
private String errorText;
// NAME data
private String firstName;
private String lastName;
private String middleName;
private LocalDate birthDate;
private LocalDate fileSinceDate;
//ID data
private String serNum;
private String idNum;
private LocalDate issDate;
private String idType;
private String idType2;
private LocalDate IdfileSinceDate;
boolean isFullFilled = false;
}

View file

@ -1,4 +1,4 @@
package ru.nbch.credit_tracker.entities.app; package ru.nbch.credit_tracker.entities.dto;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View file

@ -0,0 +1,22 @@
package ru.nbch.credit_tracker.entities.dto;
import lombok.*;
import java.time.LocalDateTime;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PhoneFidMap {
private Long id;
private Long packageId;
private String subjectId;
private String phone;
private Integer fid;
private Integer flagDebt5000;
private Integer flag90days;
private LocalDateTime calculatedAt;
private String errorText;
}

View file

@ -1,4 +1,4 @@
package ru.nbch.credit_tracker.entities.app; package ru.nbch.credit_tracker.entities.dto;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;

View file

@ -1,20 +0,0 @@
package ru.nbch.credit_tracker.entities.indic;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class Name {
private Integer fid;
private String number;
private LocalDate fileSinceDate;
private String first;
private String last;
private String middle;
private LocalDate birthDate;
}

View file

@ -1,11 +0,0 @@
package ru.nbch.credit_tracker.entities.indic;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PaymentsFlag {
private Integer fid;
private Integer flag;
}

View file

@ -4,5 +4,4 @@ public interface CommonError {
void setStatus(String status); void setStatus(String status);
void setErrorCode(String errorCode); void setErrorCode(String errorCode);
void setErrorText(String errorText); void setErrorText(String errorText);
String getErrorText();
} }

View file

@ -0,0 +1,436 @@
package ru.nbch.credit_tracker.facade;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import ru.nbch.credit_tracker.config.mdc.SetAndClearMdc;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.entities.user_request.active_packages.MonitoringActivePackagesRequest;
import ru.nbch.credit_tracker.entities.user_request.all_packages.MonitoringAllPackagesRequest;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.entities.user_response.active_packages.MonitoringActivePackagesResponse;
import ru.nbch.credit_tracker.entities.user_response.active_packages.Package;
import ru.nbch.credit_tracker.entities.user_response.active_packages.Packages;
import ru.nbch.credit_tracker.entities.user_response.all_packages.MonitoringAllPackagesResponse;
import ru.nbch.credit_tracker.entities.user_response.all_packages.PackageAll;
import ru.nbch.credit_tracker.entities.user_response.all_packages.PackagesAll;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringPackageDeleteResponse;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringReportListResponse;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringSetupResponse;
import ru.nbch.credit_tracker.enums.SignalError;
import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
import ru.nbch.credit_tracker.exceptions.SignalClientException;
import ru.nbch.credit_tracker.service.MonitoringErrorProcesingService;
import ru.nbch.credit_tracker.service.SignalService;
import ru.nbch.credit_tracker.service.scoring.AuthorizationService;
import ru.nbch.credit_tracker.service.signal.SignalRestService;
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
import ru.nbch.credit_tracker.utils.CTUtil;
import ru.nbch.credit_tracker.utils.ValidationService;
import ru.nbch.credit_tracker.utils.ZipUtils;
import javax.xml.stream.XMLStreamException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.clientExcp;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.serverExcp;
@Slf4j
@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class SignalFacade {
private SignalFacade self;
private final CreditTrackerSettings config;
private final AuthorizationService authorizationService;
private final SignalService signalService;
private final SignalRestService signalRestService;
private final MonitoringErrorProcesingService monitoringErrorProcesingService;
private final ValidationService validationService;
private final StaxXmlProcessor staxXmlProcessor;
private final ExecutorService executor;
private final long maxXmlFileSize;
public SignalFacade(CreditTrackerSettings config,
AuthorizationService authorizationService,
SignalService signalService,
SignalRestService signalRestService,
MonitoringErrorProcesingService monitoringErrorProcesingService,
ValidationService validationService,
StaxXmlProcessor staxXmlProcessor,
@Qualifier("executorService") ExecutorService executor) {
this.config = config;
this.authorizationService = authorizationService;
this.signalService = signalService;
this.signalRestService = signalRestService;
this.monitoringErrorProcesingService = monitoringErrorProcesingService;
this.validationService = validationService;
this.staxXmlProcessor = staxXmlProcessor;
this.executor = executor;
this.maxXmlFileSize = config.getMaxXmlFileSize() * 1024 * 1024;
}
public ResponseEntity<MonitoringSetupResponse> setupMonitoring(MultipartFile file) {
// первичные валидации
if (file.isEmpty() || !isZipFile(file)) {
throw clientExcp(SignalError.ERROR_001, XmlErrorFormat.MonitoringError);
}
// Валидация Архива на количетсво файлов, размер и расширение
MonitoringRequest monitoringRequest;
Long packageId;
ZipInputStream zipInputStream = null;
String filePath;
try {
zipInputStream = new ZipInputStream(file.getInputStream());
Optional<ZipEntry> xmlFileEntry = ZipUtils.extractValidEntry(zipInputStream);
if (xmlFileEntry.isEmpty()) {
throw clientExcp(SignalError.ERROR_001, XmlErrorFormat.MonitoringError);
}
if (xmlFileEntry.get().getSize() > maxXmlFileSize) {
throw clientExcp(SignalError.ERROR_009, XmlErrorFormat.MonitoringError);
}
zipInputStream.close();
zipInputStream = new ZipInputStream(file.getInputStream());
zipInputStream.getNextEntry();
filePath = writeTmpFile(zipInputStream);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
} finally {
if (zipInputStream != null) {
try {
zipInputStream.close();
} catch (IOException e) {
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
}
}
// валидация xml
try {
monitoringRequest = readAndValidateMonitoringRequest(filePath);
} catch (Exception e) {
CTUtil.removeFile(filePath);
if (e.getCause() instanceof XMLStreamException) {
throw clientExcp(SignalError.ERROR_011, XmlErrorFormat.MonitoringError);
}
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
try {
// проверка авторизации
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(monitoringRequest.getUserId(), monitoringRequest.getPassword());
if (!authResult.isSuccess()) {
signalService.rejectPackage(monitoringRequest);
throw clientExcp(authResult.getError(), monitoringRequest.getPackageName(), XmlErrorFormat.MonitoringError);
}
// проверка на уникальность пакета
if (signalService.isPackageNotUnique(monitoringRequest)) {
signalService.rejectPackage(monitoringRequest);
throw clientExcp(SignalError.ERROR_007, monitoringRequest.getPackageName(), XmlErrorFormat.MonitoringError);
}
packageId = signalService.registerPackage(monitoringRequest);
log.info("Package registered. PackageId {}. Status: {}", packageId, Status.PROCESSING.name());
} catch (Exception e) {
CTUtil.removeFile(filePath);
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
// в отдельном потоке запускаем регистрацию субъектов, поиск фидов и расчет флагов просрочек
getCurrentProxy().registerFidsAndDebts(packageId, monitoringRequest.getPackageName(), filePath);
return ResponseEntity.ok().body(new MonitoringSetupResponse());
}
private MonitoringRequest readAndValidateMonitoringRequest(String filePath) {
MonitoringRequest monitoringRequest;
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Path.of(filePath)), 8 * 1024 * 1024)) {
monitoringRequest = staxXmlProcessor.read(in, MonitoringRequest.class,
request -> {
try {
validationService.validateMonitoringRequest(request);
} catch (SignalClientException e) {
if (request != null) {
signalService.rejectPackage(request);
}
request = null;
throw e;
}
request.getSubjects().getSubject().clear();
},
request -> request.getSubjects().getSubject().isEmpty()
,
40000, "/MonitoringRequest/Subjects/Subject");
} catch (IOException e) {
throw new RuntimeException("Error while parsing and validating monitoring request", e);
}
return monitoringRequest;
}
private String writeTmpFile(ZipInputStream zipInputStream) throws IOException {
String folderPath = config.getLoadDir() + File.separator + "tmp_folder" + File.separator;
String filePath = folderPath + UUID.randomUUID() + "monitoring_request.xml";
CTUtil.createFolder(folderPath);
try (OutputStream out = Files.newOutputStream(Path.of(filePath))) {
byte[] buffer = new byte[8 * 1024 * 1024];
int bytesRead;
while ((bytesRead = zipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
return filePath;
}
}
@SetAndClearMdc(argIndex = 1)
public void registerFidsAndDebts(Long packageId, String packageName, String filePath) {
Map<String, String> context = MDC.getCopyOfContextMap();
executor.execute(() -> {
if (context != null) {
MDC.setContextMap(context);
}
try {
signalService.registerSubjects(filePath, packageId);
CTUtil.removeFile(filePath);
SignalService.UpdateResult updateResult = signalService.updateSubjects(packageId, null);
if (updateResult.isNoFidsFound()) {
log.info("No fids found for PackageId: {} PackageName: {}. Package status ON_MONITORING. No registration in signal service.", packageId, packageName);
signalService.updatePackageStatus(packageId, Status.ON_MONITORING);
return;
}
if (signalRestService.setupMonitoring(packageId)) {
LocalDateTime monitoringAt = LocalDateTime.now();
log.info("Package was successfully registered in signal service. PackageId: {} PackageName: {}, MonitoringAt: {}", packageId, packageName, monitoringAt);
signalService.updatePackageOnMonitoringSuccess(packageId, monitoringAt);
monitoringErrorProcesingService.findErrors(packageId);
} else {
log.info("Package registration was rejected by signal service. PackageId: {} PackageName: {}", packageId, packageName);
signalService.updatePackageStatus(packageId, Status.ERROR);
}
} catch (Exception e) {
CTUtil.removeFile(filePath);
signalService.updatePackageStatus(packageId, Status.ERROR);
log.error("Ошибка при поиске фидов или расчете флагов просрочек. Package_id {}", packageId, e);
}
});
}
private boolean isZipFile(MultipartFile file) {
return file.getContentType() != null &&
(file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) ||
file.getOriginalFilename() != null && file.getOriginalFilename().toLowerCase().endsWith(".zip");
}
public ResponseEntity<MonitoringActivePackagesResponse> findActivePackages(MonitoringActivePackagesRequest activePackagesRequest) {
try {
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(activePackagesRequest.getUserId(), activePackagesRequest.getPassword());
if (!authResult.isSuccess()) {
throw clientExcp(authResult.getError(), XmlErrorFormat.MonitoringActivePackagesError);
}
List<UserPackage> activePackages = signalService.findActivePackages(activePackagesRequest.getUserId());
if (!activePackages.isEmpty()) {
MonitoringActivePackagesResponse response = new MonitoringActivePackagesResponse();
response.setStatus("OK");
response.setMembercode(activePackagesRequest.getUserId().substring(0, 6));
Packages packages = new Packages();
activePackages.forEach(data -> {
Package pack = new Package();
pack.setPackageName(data.getPackageName());
pack.setCreatedAt(data.getCreatedAt() != null ? data.getCreatedAt().toString() : null);
packages.getPackages().add(pack);
});
response.setPackages(packages);
return ResponseEntity.ok(response);
} else {
throw clientExcp(SignalError.ERROR_015, XmlErrorFormat.MonitoringActivePackagesError);
}
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringActivePackagesError, e);
}
}
public ResponseEntity<MonitoringAllPackagesResponse> findAllPackages(MonitoringAllPackagesRequest allPackagesRequest) {
try {
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(allPackagesRequest.getUserId(), allPackagesRequest.getPassword());
if (!authResult.isSuccess()) {
throw clientExcp(authResult.getError(), XmlErrorFormat.MonitoringAllPackagesError);
}
List<UserPackage> activePackages = signalService.findAllPackages(allPackagesRequest.getUserId());
if (!activePackages.isEmpty()) {
MonitoringAllPackagesResponse response = new MonitoringAllPackagesResponse();
response.setStatus("OK");
response.setMembercode(allPackagesRequest.getUserId().substring(0, 6));
PackagesAll packages = new PackagesAll();
activePackages.forEach(data -> {
PackageAll pack = new PackageAll();
pack.setPackageName(data.getPackageName());
pack.setCreatedAt(data.getCreatedAt() != null ? data.getCreatedAt().toString() : null);
pack.setStatus(data.getStatus());
if (data.getStatus().equals(Status.DELETED.name())) {
pack.setDeactivatedAt(data.getDeactivatedAt() != null ? data.getDeactivatedAt().toString() : null);
}
packages.getPackages().add(pack);
});
response.setPackages(packages);
return ResponseEntity.ok(response);
} else {
throw clientExcp(SignalError.ERROR_019, XmlErrorFormat.MonitoringAllPackagesError);
}
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringAllPackagesError, e);
}
}
public ResponseEntity<MonitoringPackageDeleteResponse> deletePackage(String username, String packageName) {
try {
if (!signalService.isPackageExists(username, packageName)) {
throw clientExcp(SignalError.ERROR_016, XmlErrorFormat.MonitoringPackageDeleteError, packageName);
}
LocalDateTime deactivatedAt = LocalDateTime.now();
signalService.deletePackage(username, packageName, deactivatedAt);
MonitoringPackageDeleteResponse response = new MonitoringPackageDeleteResponse();
response.setStatus("Deleted");
response.setPackageName(packageName);
response.setDeactivatedAt(deactivatedAt.toString());
signalRestService.deletePackage(username, packageName);
log.info("Package {} deleted", packageName);
return ResponseEntity.ok(response);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_017, XmlErrorFormat.MonitoringPackageDeleteError, e);
}
}
public ResponseEntity<MonitoringReportListResponse> getReportList(String username, String packageName) {
try {
if (signalService.isPackagesByStatusExist(username, packageName, Status.PROCESSING)) {
throw clientExcp(SignalError.ERROR_018, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.REJECTED)) {
throw clientExcp(SignalError.ERROR_020, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.ERROR)) {
throw clientExcp(SignalError.ERROR_021, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.DELETED)) {
throw clientExcp(SignalError.ERROR_022, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (!signalService.isPackagesByStatusExist(username, packageName, Status.ON_MONITORING)) {
throw clientExcp(SignalError.ERROR_012, XmlErrorFormat.MonitoringReportListError, packageName);
}
String memberCode = username.substring(0, 6);
String filePrefix = (memberCode + "_" + packageName).toUpperCase();
String dirPath = config.getLoadDir() + File.separator + memberCode;
File folder = new File(dirPath);
MonitoringReportListResponse.Reports reports = new MonitoringReportListResponse.Reports();
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().startsWith(filePrefix)) {
reports.getReports().add(file.getName());
}
}
}
}
MonitoringReportListResponse response = new MonitoringReportListResponse();
response.setPackageName(packageName);
response.setReports(reports);
return ResponseEntity.ok(response);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringReportListError, e);
}
}
public ResponseEntity<Resource> downloadFile(String fileName) {
try {
String memberCode = CTUtil.getMemberCodeFromFileName(fileName);
if (memberCode == null) {
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
}
String dirPath = config.getLoadDir() + File.separator + memberCode;
File folder = new File(dirPath);
if (folder.exists() && folder.isDirectory()) {
File file = new File(dirPath + File.separator + fileName);
if (file.exists() && file.isFile()) {
return ResponseEntity.ok().body(new FileUrlResource(file.getPath()));
}
} else {
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
}
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringDownloadError, e);
}
}
private SignalFacade getCurrentProxy() {
if (self == null) {
self = (SignalFacade) AopContext.currentProxy();
}
return self;
}
}

View file

@ -1,280 +0,0 @@
package ru.nbch.credit_tracker.facade;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import javax.xml.stream.XMLStreamException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import ru.nbch.credit_tracker.component.MonitoringRequestValidator;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.app.UserPackage;
import ru.nbch.credit_tracker.entities.user_request.active_packages.MonitoringActivePackagesRequest;
import ru.nbch.credit_tracker.entities.user_request.all_packages.MonitoringAllPackagesRequest;
import ru.nbch.credit_tracker.entities.user_response.active_packages.MonitoringActivePackagesResponse;
import ru.nbch.credit_tracker.entities.user_response.active_packages.Package;
import ru.nbch.credit_tracker.entities.user_response.active_packages.Packages;
import ru.nbch.credit_tracker.entities.user_response.all_packages.MonitoringAllPackagesResponse;
import ru.nbch.credit_tracker.entities.user_response.all_packages.PackageAll;
import ru.nbch.credit_tracker.entities.user_response.all_packages.PackagesAll;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringPackageDeleteResponse;
import ru.nbch.credit_tracker.entities.user_response.success.MonitoringReportListResponse;
import ru.nbch.credit_tracker.enums.SignalError;
import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
import ru.nbch.credit_tracker.exceptions.SignalClientException;
import ru.nbch.credit_tracker.model.MonitoringValidationError;
import ru.nbch.credit_tracker.model.ValidationResult;
import ru.nbch.credit_tracker.service.SignalServiceV2;
import ru.nbch.credit_tracker.service.scoring.AuthorizationService;
import ru.nbch.credit_tracker.service.signal.SignalRestService;
import ru.nbch.credit_tracker.service.task.PipelinePackageManager;
import ru.nbch.credit_tracker.utils.CTUtil;
import ru.nbch.credit_tracker.utils.LogUtil;
import ru.nbch.credit_tracker.utils.ZipUtils;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.clientExcp;
import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.serverExcp;
@Service
@Slf4j
public class SignalFacadeV3 {
private final MonitoringRequestValidator monitoringRequestValidator;
private final CreditTrackerSettings config;
private final SignalServiceV2 signalService;
private final AuthorizationService authorizationService;
private final PipelinePackageManager pipelinePackageManager;
private final SignalRestService signalRestService;
public SignalFacadeV3(MonitoringRequestValidator monitoringRequestValidator,
CreditTrackerSettings config,
SignalServiceV2 signalService,
AuthorizationService authorizationService,
PipelinePackageManager pipelinePackageManager,
SignalRestService signalRestService) {
this.monitoringRequestValidator = monitoringRequestValidator;
this.config = config;
this.signalService = signalService;
this.authorizationService = authorizationService;
this.pipelinePackageManager = pipelinePackageManager;
this.signalRestService = signalRestService;
}
public void setupMonitoringRequest(MultipartFile file) {
log.debug("Start processing monitoring request");
monitoringRequestValidator.validateZipContent(file);
log.trace("Saving monitoring request to tmp file...");
String filePath;
try {
String folderPath = config.getLoadDir() + File.separator + "tmp_folder" + File.separator;
filePath = ZipUtils.writeTmpFile(folderPath, file);
} catch (IOException e) {
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
log.debug("File saved to {}", filePath);
ValidationResult validationResult = null;
try {
validationResult = monitoringRequestValidator.readAndValidateMonitoringRequest(filePath);
if (!validationResult.getValidationErrors().isEmpty()) {
MonitoringValidationError firstError = validationResult.getValidationErrors().get(0);
signalService.rejectPackage(validationResult.getUserId(), validationResult.getPackageName());
FileUtils.delete(new File(filePath));
throw firstError.getSignalException();
}
} catch (RuntimeException e) {
if (e.getCause() instanceof XMLStreamException) {
throw clientExcp(SignalError.ERROR_011, XmlErrorFormat.MonitoringError);
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
} catch (IOException e) {
log.warn("Error deleting tmp file", e);
}
String packageName = validationResult.getPackageName();
String userId = validationResult.getUserId();
String password = validationResult.getPassword();
try {
// проверка авторизации
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(userId, password);
if (!authResult.isSuccess()) {
signalService.rejectPackage(userId, packageName);
throw clientExcp(authResult.getError(), XmlErrorFormat.MonitoringError);
}
// проверка на уникальность пакета
// if (signalService.isPackageNotUnique(userId, packageName)) {
// signalService.rejectPackage(userId, packageName);
// throw clientExcp(SignalError.ERROR_007, XmlErrorFormat.MonitoringError);
// }
Long packageId = signalService.registerPackage(userId, packageName);
LogUtil.addMdcLogKey("%s %d".formatted(packageName, packageId));
log.info("Package registered. PackageId {}. Status: {}", packageId, Status.PROCESSING.name());
pipelinePackageManager.buildAndRun(packageId, filePath);
} catch (Exception e) {
CTUtil.removeFile(filePath);
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringError, e);
}
}
public ResponseEntity<MonitoringActivePackagesResponse> findActivePackages(MonitoringActivePackagesRequest activePackagesRequest) throws Exception {
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(activePackagesRequest.getUserId(), activePackagesRequest.getPassword());
if (!authResult.isSuccess()) {
throw clientExcp(authResult.getError(), XmlErrorFormat.MonitoringActivePackagesError);
}
List<UserPackage> activePackages = signalService.findActivePackages(activePackagesRequest.getUserId());
if (activePackages.isEmpty()) {
throw clientExcp(SignalError.ERROR_015, XmlErrorFormat.MonitoringActivePackagesError);
}
MonitoringActivePackagesResponse response = new MonitoringActivePackagesResponse();
response.setStatus("OK");
response.setMembercode(activePackagesRequest.getUserId().substring(0, 6));
Packages packages = new Packages();
activePackages.forEach(data -> {
Package pack = new Package();
pack.setPackageName(data.getPackageName());
pack.setCreatedAt(data.getCreatedAt() != null ? data.getCreatedAt().toString() : null);
packages.getPackages().add(pack);
});
response.setPackages(packages);
return ResponseEntity.ok(response);
}
public ResponseEntity<MonitoringAllPackagesResponse> findAllPackages(MonitoringAllPackagesRequest allPackagesRequest) throws Exception {
AuthorizationService.AuthResult authResult = authorizationService.findAuthInfo(allPackagesRequest.getUserId(), allPackagesRequest.getPassword());
if (!authResult.isSuccess()) {
throw clientExcp(authResult.getError(), XmlErrorFormat.MonitoringAllPackagesError);
}
List<UserPackage> activePackages = signalService.findAllPackages(allPackagesRequest.getUserId());
if (activePackages.isEmpty()) {
throw clientExcp(SignalError.ERROR_019, XmlErrorFormat.MonitoringAllPackagesError);
}
MonitoringAllPackagesResponse response = new MonitoringAllPackagesResponse();
response.setStatus("OK");
response.setMembercode(allPackagesRequest.getUserId().substring(0, 6));
PackagesAll packages = new PackagesAll();
activePackages.forEach(data -> {
PackageAll pack = new PackageAll();
pack.setPackageName(data.getPackageName());
pack.setCreatedAt(data.getCreatedAt() != null ? data.getCreatedAt().toString() : null);
pack.setStatus(data.getStatus());
if (data.getStatus().equals(Status.DELETED.name())) {
pack.setDeactivatedAt(data.getDeactivatedAt() != null ? data.getDeactivatedAt().toString() : null);
}
packages.getPackages().add(pack);
});
response.setPackages(packages);
return ResponseEntity.ok(response);
}
public ResponseEntity<MonitoringPackageDeleteResponse> deletePackage(String username, String packageName) {
try {
if (!signalService.isPackageExists(username, packageName)) {
throw clientExcp(SignalError.ERROR_016, XmlErrorFormat.MonitoringPackageDeleteError, packageName);
}
LocalDateTime deactivatedAt = LocalDateTime.now();
signalService.deletePackage(username, packageName, deactivatedAt);
MonitoringPackageDeleteResponse response = new MonitoringPackageDeleteResponse();
response.setStatus("Deleted");
response.setPackageName(packageName);
response.setDeactivatedAt(deactivatedAt.toString());
signalRestService.deletePackage(username, packageName);
log.info("Package {} deleted", packageName);
return ResponseEntity.ok(response);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_017, XmlErrorFormat.MonitoringPackageDeleteError, e);
}
}
public ResponseEntity<MonitoringReportListResponse> getReportList(String username, String packageName) {
try {
if (signalService.isPackagesByStatusExist(username, packageName, Status.PROCESSING)) {
throw clientExcp(SignalError.ERROR_018, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.REJECTED)) {
throw clientExcp(SignalError.ERROR_020, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.ERROR)) {
throw clientExcp(SignalError.ERROR_021, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (signalService.isPackagesByStatusExist(username, packageName, Status.DELETED)) {
throw clientExcp(SignalError.ERROR_022, XmlErrorFormat.MonitoringReportListError, packageName);
}
if (!signalService.isPackagesByStatusExist(username, packageName, Status.ON_MONITORING)) {
throw clientExcp(SignalError.ERROR_012, XmlErrorFormat.MonitoringReportListError, packageName);
}
String memberCode = username.substring(0, 6);
String filePrefix = (memberCode + "_" + packageName).toUpperCase();
String dirPath = config.getLoadDir() + File.separator + memberCode;
File folder = new File(dirPath);
MonitoringReportListResponse.Reports reports = new MonitoringReportListResponse.Reports();
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().startsWith(filePrefix)) {
reports.getReports().add(file.getName());
}
}
}
}
MonitoringReportListResponse response = new MonitoringReportListResponse();
response.setPackageName(packageName);
response.setReports(reports);
return ResponseEntity.ok(response);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringReportListError, e);
}
}
public ResponseEntity<Resource> downloadFile(String fileName) {
try {
String memberCode = CTUtil.getMemberCodeFromFileName(fileName);
if (memberCode == null) {
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
}
String dirPath = config.getLoadDir() + File.separator + memberCode;
File folder = new File(dirPath);
if (folder.exists() && folder.isDirectory()) {
File file = new File(dirPath + File.separator + fileName);
if (file.exists() && file.isFile()) {
return ResponseEntity.ok().body(new FileUrlResource(file.getPath()));
}
} else {
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
}
throw clientExcp(SignalError.ERROR_014, XmlErrorFormat.MonitoringDownloadError, fileName);
} catch (Exception e) {
if (e instanceof SignalClientException) {
throw (SignalClientException) e;
}
throw serverExcp(SignalError.ERROR_013, XmlErrorFormat.MonitoringDownloadError, e);
}
}
}

View file

@ -1,22 +1,19 @@
package ru.nbch.credit_tracker.mapper.indic; package ru.nbch.credit_tracker.mapper.indic;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.service.indic.FlagsService;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.entities.indic.PaymentsFlag;
import ru.nbch.credit_tracker.service.indic.FlagsService;
@Mapper @Mapper
public interface FlagsMapper { public interface FlagsMapper {
List<FlagsService.RuPastDuarearData> findRuPastDuarearData(@Param("fids") List<Integer> fids);
List<FlagsService.RuPaymtData> findRuPaymtData(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate); List<FlagsService.FlagFidData> findFlagsDebt5000(@Param("fids") List<Integer> fids);
List<PaymentsFlag> findFlagsDebt5000(@Param("fids") List<Integer> fids); List<FlagsService.FlagFidData> findFlag90ByPastDueArrear(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate);
List<PaymentsFlag> findFlag90ByPastDueArrear(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate); List<FlagsService.FlagFidData> findFlag90ByRuPaymt(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate);
List<PaymentsFlag> findFlag90ByRuPaymt(@Param("fids") List<Integer> fids, @Param("reportedDate") LocalDate reportedDate);
} }

View file

@ -0,0 +1,12 @@
package ru.nbch.credit_tracker.mapper.indic;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.service.indic.PhoneService;
import java.util.List;
@Mapper
public interface PhoneMapper {
List<PhoneService.FidsData> findFids(@Param("phones") List<String> phones);
}

View file

@ -2,7 +2,7 @@ package ru.nbch.credit_tracker.mapper.signal;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.entities.app.Hit; import ru.nbch.credit_tracker.entities.dto.Hit;
import java.util.List; import java.util.List;

View file

@ -2,29 +2,24 @@ package ru.nbch.credit_tracker.mapper.signal;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject; import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject;
import java.util.Collection;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface PhoneFidMapMapper { public interface PhoneFidMapMapper {
void registerSubjects(@Param("subjects") List<Subject> subjects, @Param("packageId") Long packageId); void registerSubjects(@Param("subjects") List<Subject> subjects, @Param("packageId") Long packageId);
void register(@Param("phoneFidMap") List<PhoneFidMap> phoneFidMap);
void registerJson(@Param("json") String json);
void registerWithId(@Param("phoneFidMap") List<PhoneFidMap> phoneFidMap);
List<PhoneFidMap> findFullPhoneFidMap(@Param("packageId") Long packageId, @Param("offset") int offset, @Param("limit") int limit); List<PhoneFidMap> findFullPhoneFidMap(@Param("packageId") Long packageId, @Param("offset") int offset, @Param("limit") int limit);
void updatePhoneFidMaps(@Param("subjects") Collection<PhoneFidMap> subjects); void updatePhoneFidMaps(@Param("subjects") List<PhoneFidMap> subjects);
void updateOne(@Param("subject") PhoneFidMap subject);
void updateFid(@Param("subjects") Collection<PhoneFidMap> subjects);
void updatePhoneFidMapsErrors(@Param("subjects") List<PhoneFidMap> subjects); void updatePhoneFidMapsErrors(@Param("subjects") List<PhoneFidMap> subjects);
PhoneFidMap findPhoneFidMapById(@Param("id") Long packageId); PhoneFidMap findPhoneFidMapById(@Param("id") Long packageId);
boolean hasNonNullFids(@Param("packageId") Long packageId); boolean hasNonNullFids(@Param("packageId") Long packageId);
boolean hasOldRow(@Param("packageId") Long packageId, @Param("daysCount") Integer daysCount);
} }

View file

@ -2,7 +2,7 @@ package ru.nbch.credit_tracker.mapper.signal;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;

View file

@ -1,16 +0,0 @@
package ru.nbch.credit_tracker.model;
public class DummyPayload implements StagePayload {
private final boolean poison;
public static DummyPayload POISON = new DummyPayload(true);
public DummyPayload(boolean poison) {
this.poison = poison;
}
@Override
public boolean isPoison() {
return poison;
}
}

View file

@ -1,37 +0,0 @@
package ru.nbch.credit_tracker.model;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap;
import java.util.List;
public class FlagsPayload implements StagePayload {
private final Long packageId;
private final List<PhoneFidMap> phoneFidMaps;
private final boolean poison;
public FlagsPayload(Long packageId, List<PhoneFidMap> fidsMapByBatch) {
this.packageId = packageId;
this.phoneFidMaps = fidsMapByBatch;
this.poison = false;
}
public FlagsPayload(boolean poison) {
this.packageId = null;
this.phoneFidMaps = null;
this.poison = poison;
}
public Long getPackageId() {
return packageId;
}
public List<PhoneFidMap> getPhoneFidMaps() {
return phoneFidMaps;
}
@Override
public boolean isPoison() {
return poison;
}
}

View file

@ -1,21 +0,0 @@
package ru.nbch.credit_tracker.model;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
//todo подумать над неймингом класс
public class IdData {
private Integer fid;
private String serNum;
private String idNum;
private LocalDate issDate;
private String idType;
private String idType2;
private LocalDate fileSinceDate;
}

View file

@ -1,30 +0,0 @@
package ru.nbch.credit_tracker.model;
import ru.nbch.credit_tracker.exceptions.SignalException;
public class MonitoringValidationError {
private final int line;
private final String message;
private final SignalException signalException;
public MonitoringValidationError(int line,
String message,
SignalException signalException) {
this.line = line;
this.message = message;
this.signalException = signalException;
}
public int getLine() {
return line;
}
public String getMessage() {
return message;
}
public SignalException getSignalException() {
return signalException;
}
}

View file

@ -1,20 +0,0 @@
package ru.nbch.credit_tracker.model;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
//todo подумать над неймингом класс
public class NameData {
private Integer fid;
private String firstName;
private String lastName;
private String middleName;
private LocalDate birthDate;
private LocalDate fileSinceDate;
}

View file

@ -1,16 +0,0 @@
package ru.nbch.credit_tracker.model;
public class SendSignalsPayload implements StagePayload {
private String signalXmlPath;
public static final SendSignalsPayload POISON = new SendSignalsPayload(true);
private boolean poison;
public SendSignalsPayload(boolean poison) {
this.poison = poison;
}
@Override
public boolean isPoison() {
return poison;
}
}

View file

@ -1,5 +0,0 @@
package ru.nbch.credit_tracker.model;
public interface StagePayload {
boolean isPoison();
}

View file

@ -1,17 +0,0 @@
package ru.nbch.credit_tracker.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class SubjectProfile {
private String identifier;
private String phone;
private Integer fid;
private NameData nameData;
private IdData idData;
}

View file

@ -1,4 +0,0 @@
package ru.nbch.credit_tracker.model;
public interface TerminatePayload {
}

View file

@ -1,51 +0,0 @@
package ru.nbch.credit_tracker.model;
import java.util.ArrayList;
import java.util.List;
public class ValidationResult {
private String packageName;
private String userId;
private String password;
private List<MonitoringValidationError> validationErrors = new ArrayList<>();
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setValidationErrors(List<MonitoringValidationError> validationErrors) {
this.validationErrors = validationErrors;
}
public ValidationResult() {
}
public String getPackageName() {
return packageName;
}
public String getUserId() {
return userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<MonitoringValidationError> getValidationErrors() {
return validationErrors;
}
public void addError(MonitoringValidationError error) {
this.validationErrors.add(error);
}
}

View file

@ -1,30 +0,0 @@
package ru.nbch.credit_tracker.model.impl;
import java.nio.file.Path;
import lombok.Getter;
import ru.nbch.credit_tracker.model.StagePayload;
@Getter
public class PartFilePayload implements StagePayload {
private final Path partPath;
private final Long packageId;
private final boolean poison;
public static final PartFilePayload POISON = new PartFilePayload(true);
public PartFilePayload(boolean poison) {
this.poison = poison;
this.partPath = null;
this.packageId = null;
}
public PartFilePayload(Path partPath, Long packageId) {
this.partPath = partPath;
this.packageId = packageId;
this.poison = false;
}
@Override
public boolean isPoison() {
return poison;
}
}

View file

@ -1,16 +0,0 @@
package ru.nbch.credit_tracker.model.impl;
import java.nio.file.Path;
import ru.nbch.credit_tracker.model.TerminatePayload;
public class SignalXmlFilePayload implements TerminatePayload {
private final Path signalXmlFilePath;
public SignalXmlFilePayload(Path signalXmlFilePath) {
this.signalXmlFilePath = signalXmlFilePath;
}
public Path getSignalXmlFilePath() {
return signalXmlFilePath;
}
}

View file

@ -1,38 +0,0 @@
package ru.nbch.credit_tracker.model.impl;
import java.util.List;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.model.SubjectProfile;
public class SubjectPayload implements StagePayload {
private final Long packageId;
private final List<SubjectProfile> subjects;
public static final SubjectPayload POISON = new SubjectPayload(true);
private final boolean poison;
public SubjectPayload(boolean poison) {
this.packageId = null;
this.subjects = null;
this.poison = poison;
}
public SubjectPayload(Long packageId, List<SubjectProfile> subjects) {
this.packageId = packageId;
this.subjects = subjects;
this.poison = false;
}
public Long getPackageId() {
return packageId;
}
public List<SubjectProfile> getSubjects() {
return subjects;
}
@Override
public boolean isPoison() {
return poison;
}
}

View file

@ -1,150 +0,0 @@
package ru.nbch.credit_tracker.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import ru.nbch.credit_tracker.entities.indic.Name;
@Repository
public class PhoneRepository {
private static final String SELCT_FIDS = """
SELECT
pn.number,
pn.fid,
pn.file_since_dt
FROM PHONE_NORM pn
JOIN (SELECT DISTINCT fid FROM NAME) nf
ON nf.fid = pn.fid
WHERE number in (:phones)
ORDER BY
pn.number ASC,
pn.file_since_dt DESC,
pn.fid DESC
WITH UR
""";
private static final String SELCT_FIDS_WITH_NAME = """
SELECT
pn.number,
pn.fid,
pn.file_since_dt,
nf.first,
nf.middle,
nf.name_1,
nf.birth_dt,
nf.file_since_dt as file_since_dt_name
FROM PHONE_NORM pn
JOIN NAME nf
ON nf.fid = pn.fid
WHERE number in (:phones) WITH UR
""";
private final NamedParameterJdbcTemplate jdbcTemplate;
public PhoneRepository(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<FidsData> findFids(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new PhoneRepository.FidsData(rs.getInt("fid"), rs.getString("number"), rs.getObject("file_since_dt", LocalDate.class))
);
}
@Deprecated
public List<FidsDataWithName> findFidsWithName(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS_WITH_NAME, params, (rs, rowNum) ->
new PhoneRepository.FidsDataWithName(
rs.getInt("FID"),
rs.getString("NUMBER"),
getLocalDate(rs, "FILE_SINCE_DT"),
rs.getString("FIRST"),
rs.getString("NAME_1"),
rs.getString("MIDDLE"),
getLocalDate(rs, "BIRTH_DT"),
getLocalDate(rs, "FILE_SINCE_DT_NAME")
));
}
public List<Name> findNameByPhones(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS_WITH_NAME, params, (rs, rowNum) ->
Name.builder()
.fid(rs.getInt("FID"))
.number(rs.getString("NUMBER"))
.fileSinceDate(getLocalDate(rs, "FILE_SINCE_DT_NAME"))
.first(rs.getString("FIRST"))
.middle(rs.getString("MIDDLE"))
.last(rs.getString("NAME_1"))
.birthDate(getLocalDate(rs, "BIRTH_DT"))
.build());
}
private static LocalDate getLocalDate(ResultSet rs, String col) throws SQLException {
java.sql.Date d = rs.getDate(col);
return d != null ? d.toLocalDate() : null;
}
public List<FidsDataLong> findFidsLong(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new PhoneRepository.FidsDataLong(rs.getInt("fid"), Long.parseLong(rs.getString("number")), rs.getObject("file_since_dt", LocalDate.class))
);
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsData {
private Integer fid;
private String phone;
private LocalDate fileSinceDate;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsDataWithName {
private Integer fid;
private String phone;
private LocalDate fileSinceDate;
private String firstName;
private String lastName;
private String middleName;
private LocalDate birthDate;
private LocalDate fileSinceDateName;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsDataLong {
private Integer fid;
private Long phone;
private LocalDate fileSinceDate;
}
}

View file

@ -6,7 +6,7 @@ import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.mdc.SetAndClearMdc; import ru.nbch.credit_tracker.config.mdc.SetAndClearMdc;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.service.signal.SignalRestService; import ru.nbch.credit_tracker.service.signal.SignalRestService;
import ru.nbch.credit_tracker.utils.CTUtil; import ru.nbch.credit_tracker.utils.CTUtil;

View file

@ -6,9 +6,9 @@ import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.app.Hit; import ru.nbch.credit_tracker.entities.dto.Hit;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.entities.external_response.online_download.Inquiry; import ru.nbch.credit_tracker.entities.external_response.online_download.Inquiry;
import ru.nbch.credit_tracker.entities.external_response.online_download.Person; import ru.nbch.credit_tracker.entities.external_response.online_download.Person;
import ru.nbch.credit_tracker.entities.external_response.online_download.Persons; import ru.nbch.credit_tracker.entities.external_response.online_download.Persons;

View file

@ -6,16 +6,18 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.builder.PersonFactoryBuilder; import ru.nbch.credit_tracker.builder.PersonFactoryBuilder;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings; import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.app.Hit; import ru.nbch.credit_tracker.entities.dto.Hit;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.entities.external_request.signals.Auth; import ru.nbch.credit_tracker.entities.external_request.signals.Auth;
import ru.nbch.credit_tracker.entities.external_request.signals.Person; import ru.nbch.credit_tracker.entities.external_request.signals.Person;
import ru.nbch.credit_tracker.entities.external_request.signals.Signals; import ru.nbch.credit_tracker.entities.external_request.signals.Signals;
import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors; import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest; import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.indic.*; import ru.nbch.credit_tracker.service.indic.FlagsService;
import ru.nbch.credit_tracker.service.indic.PersonService;
import ru.nbch.credit_tracker.service.indic.PhoneService;
import ru.nbch.credit_tracker.service.signal.HitsService; import ru.nbch.credit_tracker.service.signal.HitsService;
import ru.nbch.credit_tracker.service.signal.PhoneFidMapService; import ru.nbch.credit_tracker.service.signal.PhoneFidMapService;
import ru.nbch.credit_tracker.service.signal.UserPackageService; import ru.nbch.credit_tracker.service.signal.UserPackageService;
@ -35,15 +37,12 @@ import java.util.function.Predicate;
@Slf4j @Slf4j
@Service @Service
@Deprecated
//todo оставил для временного подсмотреть на старую логику
public class SignalService { public class SignalService {
private final CreditTrackerSettings config; private final CreditTrackerSettings config;
private final HitsService hitsService; private final HitsService hitsService;
private final UserPackageService userPackageService; private final UserPackageService userPackageService;
private final PhoneFidMapService phoneFidMapService; private final PhoneFidMapService phoneFidMapService;
private final PhoneService2 phoneService2;
private final PhoneService phoneService; private final PhoneService phoneService;
private final FlagsService flagsService; private final FlagsService flagsService;
private final PersonService personService; private final PersonService personService;
@ -55,7 +54,6 @@ public class SignalService {
HitsService hitsService, HitsService hitsService,
UserPackageService userPackageService, UserPackageService userPackageService,
PhoneFidMapService phoneFidMapService, PhoneFidMapService phoneFidMapService,
PhoneService2 phoneService2,
PhoneService phoneService, PhoneService phoneService,
FlagsService flagsService, FlagsService flagsService,
PersonService personService, PersonService personService,
@ -64,7 +62,6 @@ public class SignalService {
this.hitsService = hitsService; this.hitsService = hitsService;
this.userPackageService = userPackageService; this.userPackageService = userPackageService;
this.phoneFidMapService = phoneFidMapService; this.phoneFidMapService = phoneFidMapService;
this.phoneService2 = phoneService2;
this.phoneService = phoneService; this.phoneService = phoneService;
this.flagsService = flagsService; this.flagsService = flagsService;
this.personService = personService; this.personService = personService;
@ -75,10 +72,6 @@ public class SignalService {
return userPackageService.isPackageNotUnique(request.getUserId(), request.getPackageName()); return userPackageService.isPackageNotUnique(request.getUserId(), request.getPackageName());
} }
public boolean isPackageNotUnique(String userId, String packageName) {
return userPackageService.isPackageNotUnique(userId, packageName);
}
public Long registerPackage(MonitoringRequest request) { public Long registerPackage(MonitoringRequest request) {
UserPackage userPackage = UserPackage.builder() UserPackage userPackage = UserPackage.builder()
.userId(request.getUserId()) .userId(request.getUserId())
@ -89,16 +82,6 @@ public class SignalService {
return userPackageService.insertPackage(userPackage); return userPackageService.insertPackage(userPackage);
} }
public Long registerPackage(String userId, String packageName) {
UserPackage userPackage = UserPackage.builder()
.userId(userId)
.packageName(packageName)
.createdAt(LocalDateTime.now())
.status(Status.PROCESSING.name())
.build();
return userPackageService.insertPackage(userPackage);
}
public void registerSubjects(String filePath, Long packageId) { public void registerSubjects(String filePath, Long packageId) {
log.info("Start registering subjects. PackageId: {}", packageId); log.info("Start registering subjects. PackageId: {}", packageId);
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Path.of(filePath)), 8 * 1024 * 1024)) { try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Path.of(filePath)), 8 * 1024 * 1024)) {
@ -119,25 +102,6 @@ public class SignalService {
log.info("Subjects were registered. PackageId: {}", packageId); log.info("Subjects were registered. PackageId: {}", packageId);
} }
/**
* Выгружает {@link PhoneFidMap} батчами. Передает обогащенные фидами и данными из Name {@link PhoneFidMap}
* на поиск флагов и формирование отчета в оф. сервис сигналов
* @param packageId id пакета, по которому идет выборка {@link PhoneFidMap}
*/
public void processSubjects(Long packageId) {
int offset = 0;
List<PhoneFidMap> batch;
do {
batch = phoneFidMapService.findFullPhoneFidMap(packageId, offset, batchSize);
if (!batch.isEmpty()) {
Map<Integer, PhoneFidMap> fidsMapByBatch = phoneService.defineFidsAndNameByPhone(batch);
// TODO process fidsMapByBatch here
}
offset += batchSize;
} while (!batch.isEmpty());
}
/** /**
* Обрабатывает все PhoneFidMap по packageId, выгружая их пачками и фильтруя по переданному фильтру * Обрабатывает все PhoneFidMap по packageId, выгружая их пачками и фильтруя по переданному фильтру
*/ */
@ -146,7 +110,6 @@ public class SignalService {
boolean noFidsFound = true; boolean noFidsFound = true;
int offset = 0; int offset = 0;
List<PhoneFidMap> batch; List<PhoneFidMap> batch;
Map<Integer, PhoneFidMap> phonesByFidToProcessing = new HashMap<>();
do { do {
batch = phoneFidMapService.findFullPhoneFidMap(packageId, offset, batchSize); batch = phoneFidMapService.findFullPhoneFidMap(packageId, offset, batchSize);
@ -162,32 +125,15 @@ public class SignalService {
} }
if (!filteredItems.isEmpty()) { if (!filteredItems.isEmpty()) {
Map<Integer, PhoneFidMap> fidsMapByBatch = phoneService.defineFidByPhone(filteredItems); boolean fidsFound = updateFlags(filteredItems, packageId);
phoneService.defineFidByPhone(filteredItems);
log.debug("Actual fids size by phone's batch: {}", fidsMapByBatch.size());
if (fidsMapByBatch.size() + phonesByFidToProcessing.size() <= batchSize) {
phonesByFidToProcessing.putAll(fidsMapByBatch);
} else {
//todo execute find name and id -> forming xml
boolean fidsFound = updateFlags(phonesByFidToProcessing, packageId);
if (noFidsFound) { if (noFidsFound) {
noFidsFound = !fidsFound; noFidsFound = !fidsFound;
} }
phonesByFidToProcessing = new HashMap<>(fidsMapByBatch);
}
} }
offset += batchSize; offset += batchSize;
} while (!batch.isEmpty()); } while (!batch.isEmpty());
if (!phonesByFidToProcessing.isEmpty()) {
boolean fidsFound = updateFlags(phonesByFidToProcessing, packageId);
if (noFidsFound) {
noFidsFound = !fidsFound;
}
}
return new UpdateResult(updated, noFidsFound); return new UpdateResult(updated, noFidsFound);
} }
@ -195,37 +141,50 @@ public class SignalService {
* Обрабатывает пачку PhoneFidMap: обновляет фиды и флаги просрочек * Обрабатывает пачку PhoneFidMap: обновляет фиды и флаги просрочек
* @return fidsFound true - если хотя бы по одному номеру был найден фид, false - в противном случае * @return fidsFound true - если хотя бы по одному номеру был найден фид, false - в противном случае
*/ */
// TODO Добавить отдельный метод. Если не нашелся фид, надо обновить в базе fid = null и флаги = null calculated_at=now() public boolean updateFlags(List<PhoneFidMap> subjects, Long packageId) {
public boolean updateFlags(Map<Integer, PhoneFidMap> fidsMap, Long packageId) { log.info("Start calculating fids and flags for subjects batch. PackageId: {}", packageId);
log.info("Start calculating flags for subjects batch. PackageId: {}", packageId); Map<String, Integer> fidsMap = phoneService.findFidsPhoneData(subjects);
Map<Integer, FlagsService.FlagsData> flagsDataMap = flagsService.findFlags(fidsMap.keySet().stream().toList()); LocalDateTime calculatedAt = LocalDateTime.now();
boolean fidsFound = fidsMap.values().stream().anyMatch(Objects::nonNull);
Map<Integer, FlagsService.FlagsData> flagsDataMap = flagsService.findFlags(fidsMap.values().stream().filter(Objects::nonNull).toList());
List<PhoneFidMap> subjectToUpdate = new ArrayList<>(); List<PhoneFidMap> subjectToUpdate = new ArrayList<>();
for (Map.Entry<Integer, FlagsService.FlagsData> entry : flagsDataMap.entrySet()) {
Integer fid = entry.getKey();
FlagsService.FlagsData flagsData = entry.getValue();
PhoneFidMap phoneFidMap = fidsMap.get(fid);
phoneFidMap.setFid(fid); subjects.forEach(subject -> {
phoneFidMap.setFlagDebt5000(flagsData.getFlagDebt5000()); Integer fid = fidsMap.get("9" + subject.getPhone());
phoneFidMap.setFlag90days(flagsData.getFlag90days()); if (fid != null) {
phoneFidMap.setCalculatedAt(LocalDateTime.now()); FlagsService.FlagsData flagsData = flagsDataMap.get(fid);
subject = PhoneFidMap.builder()
subjectToUpdate.add(phoneFidMap); .id(subject.getId())
.packageId(packageId)
.subjectId(subject.getSubjectId())
.phone(subject.getPhone())
.fid(fid)
.flagDebt5000(flagsData.getFlagDebt5000())
.flag90days(flagsData.getFlag90days())
.calculatedAt(calculatedAt)
.build();
} else {
subject = PhoneFidMap.builder()
.id(subject.getId())
.packageId(packageId)
.subjectId(subject.getSubjectId())
.phone(subject.getPhone())
.calculatedAt(calculatedAt)
.build();
} }
subjectToUpdate.add(subject);
});
log.debug("Start updating subject's batch"); int updateBatchSize = batchSize / 9; // 9 параметров на одну запись для обновления. Максимальное количество параметров ~65000
if (!subjectToUpdate.isEmpty()) {
int updateBatchSize = 7000; // 9 параметров на одну запись для обновления. Максимальное количество параметров ~65000
for (int j = 0; j < subjectToUpdate.size(); j += updateBatchSize) { for (int j = 0; j < subjectToUpdate.size(); j += updateBatchSize) {
int endIndex = Math.min(j + updateBatchSize, subjectToUpdate.size()); int endIndex = Math.min(j + updateBatchSize, subjectToUpdate.size());
phoneFidMapService.updatePhoneFidMaps(subjectToUpdate.subList(j, endIndex)); phoneFidMapService.updatePhoneFidMaps(subjectToUpdate.subList(j, endIndex));
log.trace("Updated fids and flags for subject's batch from: {} to: {}. Total count: {}. PackageId: {}", j, endIndex, subjectToUpdate.size(), packageId); log.trace("Updated fids and flags for subject's batch from: {} to: {}. Total count: {}. PackageId: {}", j, endIndex, subjectToUpdate.size(), packageId);
} }
}
log.debug("Complete updating subject's batch");
log.info("Finish calculating flags for subjects. PackageId: {}. Batch size: {}", packageId, fidsMap.size()); log.info("Finish calculating fids and flags for subjects. PackageId: {}. Batch size: {}", packageId, subjects.size());
return !fidsMap.isEmpty();
return fidsFound;
} }
public void rejectPackage(MonitoringRequest request) { public void rejectPackage(MonitoringRequest request) {
@ -238,16 +197,6 @@ public class SignalService {
userPackageService.insertPackage(userPackage); userPackageService.insertPackage(userPackage);
} }
public void rejectPackage(String userId, String packageName) {
UserPackage userPackage = UserPackage.builder()
.userId(userId)
.packageName(packageName)
.createdAt(LocalDateTime.now())
.status(Status.REJECTED.name())
.build();
userPackageService.insertPackage(userPackage);
}
public void updatePackageStatus(Long packageId, Status status) { public void updatePackageStatus(Long packageId, Status status) {
userPackageService.updatePackageStatus(packageId, status); userPackageService.updatePackageStatus(packageId, status);
} }
@ -277,7 +226,7 @@ public class SignalService {
return filePath; return filePath;
} }
public Signals buildSignals(Long packageId) { private Signals buildSignals(Long packageId) {
UserPackage userPackage = userPackageService.findPackage(packageId); UserPackage userPackage = userPackageService.findPackage(packageId);
Signals signals = new Signals(); Signals signals = new Signals();
signals.setOnline("2"); // Всегда=2 (только онлайн-сигналы) signals.setOnline("2"); // Всегда=2 (только онлайн-сигналы)

View file

@ -1,70 +0,0 @@
package ru.nbch.credit_tracker.service;
import java.time.LocalDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.app.UserPackage;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.signal.UserPackageService;
@Slf4j
@Service
public class SignalServiceV2 {
private final UserPackageService userPackageService;
public SignalServiceV2(UserPackageService userPackageService) {
this.userPackageService = userPackageService;
}
public boolean isPackageNotUnique(MonitoringRequest request) {
return userPackageService.isPackageNotUnique(request.getUserId(), request.getPackageName());
}
public boolean isPackageNotUnique(String userId, String packageName) {
return userPackageService.isPackageNotUnique(userId, packageName);
}
public Long registerPackage(String userId, String packageName) {
UserPackage userPackage = UserPackage.builder()
.userId(userId)
.packageName(packageName)
.createdAt(LocalDateTime.now())
.status(Status.PROCESSING.name())
.build();
return userPackageService.insertPackage(userPackage);
}
public void rejectPackage(String userId, String packageName) {
UserPackage userPackage = UserPackage.builder()
.userId(userId)
.packageName(packageName)
.createdAt(LocalDateTime.now())
.status(Status.REJECTED.name())
.build();
userPackageService.insertPackage(userPackage);
}
public List<UserPackage> findActivePackages(String userId) {
return userPackageService.findUserPackages(userId, Status.ON_MONITORING);
}
public List<UserPackage> findAllPackages(String userId) {
return userPackageService.findAllPackagesByUserId(userId);
}
public boolean isPackageExists(String username, String packageName) {
return userPackageService.isPackageExists(username, packageName, Status.ON_MONITORING);
}
public boolean isPackagesByStatusExist(String username, String packageName, Status status) {
return userPackageService.hasActivePackages(username, packageName, status);
}
public void deletePackage(String username, String packageName, LocalDateTime deactivatedAt) {
userPackageService.deletePackage(username, packageName, Status.DELETED, Status.ON_MONITORING, deactivatedAt);
}
}

View file

@ -5,7 +5,7 @@ import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.mdc.SetAndClearMdc; import ru.nbch.credit_tracker.config.mdc.SetAndClearMdc;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.signal.SignalRestService; import ru.nbch.credit_tracker.service.signal.SignalRestService;

View file

@ -1,22 +1,18 @@
package ru.nbch.credit_tracker.service; package ru.nbch.credit_tracker.service;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap;
import ru.nbch.credit_tracker.entities.app.UserPackage;
import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.service.signal.SignalRestService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ScheduledExecutorService; import org.slf4j.MDC;
import java.util.concurrent.TimeUnit; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.function.Predicate; import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.service.signal.SignalRestService;
/** /**
* Сервис периодического обновления фидов и флагов просрочек по пакетам находящимся в статусе ON_MONITORING * Сервис периодического обновления фидов и флагов просрочек по пакетам находящимся в статусе ON_MONITORING
@ -30,31 +26,40 @@ public class UpdatingFlagsService {
private final SignalRestService signalRestService; private final SignalRestService signalRestService;
private final MonitoringErrorProcesingService monitoringErrorProcesingService; private final MonitoringErrorProcesingService monitoringErrorProcesingService;
private final Long daysToUpdateFlags; private final Long daysToUpdateFlags;
private final Long updateFlagsInterval; private final String updateFlagsCronExpression;
private final PhoneFidMapMapper phoneFidMapMapper;
public UpdatingFlagsService(SignalService signalService, public UpdatingFlagsService(SignalService signalService,
SignalRestService signalRestService, SignalRestService signalRestService,
MonitoringErrorProcesingService monitoringErrorProcesingService, MonitoringErrorProcesingService monitoringErrorProcesingService,
CreditTrackerSettings config) { CreditTrackerSettings config,
PhoneFidMapMapper phoneFidMapMapper) {
this.signalService = signalService; this.signalService = signalService;
this.signalRestService = signalRestService; this.signalRestService = signalRestService;
this.monitoringErrorProcesingService = monitoringErrorProcesingService; this.monitoringErrorProcesingService = monitoringErrorProcesingService;
this.daysToUpdateFlags = config.getDaysToUpdateFlags() != null ? config.getDaysToUpdateFlags() : 1; this.daysToUpdateFlags = config.getDaysToUpdateFlags() != null ? config.getDaysToUpdateFlags() : 1;
this.updateFlagsInterval = config.getUpdateFlagsInterval() != null ? config.getUpdateFlagsInterval() : 1; this.updateFlagsCronExpression = config.getUpdateFlagsCronExpression() != null ? config.getUpdateFlagsCronExpression() : "*/10 * * * * *";
this.phoneFidMapMapper = phoneFidMapMapper;
} }
public void run() { public void run() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("updating-flags-")); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.scheduleAtFixedRate(this::update, 0, updateFlagsInterval, TimeUnit.DAYS); scheduler.setPoolSize(1);
scheduler.initialize();
scheduler.schedule(this::update, new CronTrigger(updateFlagsCronExpression));
} }
private void update() { public void update() {
log.info("Start updating flags"); log.info("Start updating flags");
List<UserPackage> packages = signalService.findPackagesOnMonitoring(); List<UserPackage> packages = signalService.findPackagesOnMonitoring();
for (UserPackage userPackage : packages) { for (UserPackage userPackage : packages) {
log.info("Updating flags for Package {}", userPackage.getPackageName());
MDC.put("ru.nbch.credit_tracker.log.mdc.key", "[" + userPackage.getPackageName() +"]"); MDC.put("ru.nbch.credit_tracker.log.mdc.key", "[" + userPackage.getPackageName() +"]");
log.info("Check on updating flags for Package {}", userPackage.getPackageName());
boolean hasOldRow = phoneFidMapMapper.hasOldRow(userPackage.getId(), daysToUpdateFlags.intValue());
log.info("There are {} old rows", hasOldRow ? "exist" : "doesn't exist");
if (hasOldRow) {
updateFlags(userPackage); updateFlags(userPackage);
}
MDC.remove("ru.nbch.credit_tracker.log.mdc.key"); MDC.remove("ru.nbch.credit_tracker.log.mdc.key");
} }
log.info("Finish updating flags"); log.info("Finish updating flags");
@ -65,8 +70,7 @@ public class UpdatingFlagsService {
String packageName = userPackage.getPackageName(); String packageName = userPackage.getPackageName();
try { try {
signalService.updatePackageStatus(packageId, Status.PROCESSING); signalService.updatePackageStatus(packageId, Status.PROCESSING);
Predicate<PhoneFidMap> filter = subject -> subject.getCalculatedAt() != null && hasDaysPassed(subject.getCalculatedAt()); SignalService.UpdateResult updateResult = signalService.updateSubjects(packageId, null);
SignalService.UpdateResult updateResult = signalService.updateSubjects(packageId, filter);
if (updateResult.isUpdated()) { if (updateResult.isUpdated()) {
if (signalRestService.setupMonitoring(packageId)) { if (signalRestService.setupMonitoring(packageId)) {
LocalDateTime monitoringAt = userPackage.getMonitoringAt() != null ? userPackage.getMonitoringAt() : LocalDateTime.now(); LocalDateTime monitoringAt = userPackage.getMonitoringAt() != null ? userPackage.getMonitoringAt() : LocalDateTime.now();

View file

@ -1,70 +1,35 @@
package ru.nbch.credit_tracker.service.indic; package ru.nbch.credit_tracker.service.indic;
import java.sql.ResultSet;
import java.sql.SQLException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.mapper.indic.FlagsMapper; import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.*; import java.util.HashMap;
import ru.nbch.credit_tracker.model.SubjectProfile; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service @Service
public class FlagsService { public class FlagsService {
private static final Logger log = LoggerFactory.getLogger(FlagsService.class);
private final FlagsMapper mapper; private final FlagsMapper mapper;
private static final String SELECT_RU_PASDUEARREAR_DATA = public FlagsService(FlagsMapper mapper) {
"SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE, DAYS_PAST_DUE " +
"FROM RU_PASTDUEARREAR " +
"WHERE FID IN (:fids) " +
"ORDER BY FID, REPORTED_DT DESC " +
"WITH UR";
private static final String SELECT_RU_PAYMT_DATA =
"SELECT FID, DAYS_PAST_DUE, REPORTED_DT " +
"FROM RU_PAYMT " +
"WHERE FID IN (:fids) " +
"AND REPORTED_DT >= :reportedDate " +
"WITH UR";
private final NamedParameterJdbcTemplate jdbcTemplate;
public FlagsService(FlagsMapper mapper,
@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.mapper = mapper; this.mapper = mapper;
this.jdbcTemplate = jdbcTemplate;
} }
public Map<Integer, FlagsData> findFlags(List<Integer> fids) { public Map<Integer, FlagsData> findFlags(List<Integer> fids) {
if (fids.isEmpty()) { if (fids.isEmpty()) {
return new HashMap<>(); return new HashMap<>();
} }
List<Integer> distinctFids = fids.stream()
.distinct()
.sorted(Comparator.naturalOrder())
.toList();
log.debug("Fids count: {}", distinctFids.size());
Map<Integer, Integer> flags5000 = new HashMap<>();
Map<Integer, Integer> flags90DaysByPastDueArrear = new HashMap<>();
Map<Integer, Integer> flags90DaysByRuPaymt = new HashMap<>();
findFlagsRuPastDueArrear(distinctFids, flags5000, flags90DaysByPastDueArrear);
findFlagsRuPaymt(distinctFids, flags90DaysByRuPaymt);
Map<Integer, FlagsData> result = new HashMap<>(); Map<Integer, FlagsData> result = new HashMap<>();
Map<Integer, Integer> flags5000 = calcFlag5000(fids);
Map<Integer, Integer> flags90DaysByPastDueArrear = calcFlag90DaysByPastDueArrear(fids);
Map<Integer, Integer> flags90DaysByRuPaymt = calcFlag90DaysByRuPaymt(fids);
fids.forEach(fid -> { fids.forEach(fid -> {
Integer flagDebt5000 = flags5000.get(fid); Integer flagDebt5000 = flags5000.get(fid);
if (flagDebt5000 == null) { if (flagDebt5000 == null) {
@ -72,7 +37,7 @@ public class FlagsService {
} }
Integer flag90days = flags90DaysByPastDueArrear.get(fid); Integer flag90days = flags90DaysByPastDueArrear.get(fid);
if (flag90days == null || flag90days == 0) { if (flag90days == null) {
flag90days = flags90DaysByRuPaymt.get(fid); flag90days = flags90DaysByRuPaymt.get(fid);
if (flag90days == null) { if (flag90days == null) {
flag90days = 0; flag90days = 0;
@ -84,352 +49,52 @@ public class FlagsService {
return result; return result;
} }
// TODO optimize
public void fillFlagsData(List<SubjectProfile> subjects) {
List<Integer> fids = subjects.stream().map(SubjectProfile::getFid).filter(Objects::nonNull).toList();
Map<Integer, FlagsService.FlagsData> fidsflagsDataMap = findFlags2(fids);
fidsflagsDataMap.forEach((fid, data) -> {
subjects.stream().filter(s-> s.getFid() != null && s.getFid().equals(fid)).forEach(phoneFidMap -> {
// phoneFidMap.setFlagDebt5000(data.getFlagDebt5000());
// phoneFidMap.setFlag90days(data.getFlag90days());
// phoneFidMap.setCalculatedAt(LocalDateTime.now());
});
});
subjects.stream().filter(s-> s.getFid() == null).forEach(phoneFidMap -> {
// phoneFidMap.setCalculatedAt(LocalDateTime.now());
});
}
/** /**
* Выбирает данные из RU_PASTDUEARREAR и ищет по ним флаги просрочек
* По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT. * По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT.
* Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то flag500 устанавливается в 1. * Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то флаг устанавливается в 1.
* Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0. * Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0.
*
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PASTDUEARREAR у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
* @param fids - отсортирован по возрастанию фидов
* @param flag5000Map - рещультирующая мапа флагов просрочек debt5000
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/ */
private void findFlagsRuPastDueArrear(List<Integer> fids, Map<Integer, Integer> flag5000Map, Map<Integer, Integer> flag90Days) { private Map<Integer, Integer> calcFlag5000(List<Integer> fids) {
log.debug("Start calculating flag 5000 and 90 days by RU_PASTDUEARREAR"); return mapper.findFlagsDebt5000(fids).stream()
List<RuPastDuarearData> ruPastDuarearDataList = mapper.findRuPastDuarearData(fids); .collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
(existing, replacement) -> {
ruPastDuarearDataList.sort(Comparator.comparing(FlagsService.RuPastDuarearData::getFid) // сортируем сначала по фиду if (existing == 1) {
.thenComparing(FlagsService.RuPastDuarearData::getReportedDate, Comparator.reverseOrder())); // Затем сортируем по REPORTED_DT по убыванию return existing;
LocalDate yearAgo = LocalDate.now().minusYears(1);
for (Integer fid : fids) {
Iterator<RuPastDuarearData> iterator = ruPastDuarearDataList.iterator();
BigInteger prevAccSerialNum = null;
while (iterator.hasNext()) {
FlagsService.RuPastDuarearData data = iterator.next();
if (fid.equals(data.getFid())) {
if (flag5000Map.get(fid) == null) { // Первая запись в списке с макс REPORTED_DT. Нас интересует только она для флага 5000
if (prevAccSerialNum == null || !prevAccSerialNum.equals(data.getAccSerialNum())) {
if (data.getAmtPastDue() != null && data.getAmtPastDue().compareTo(BigDecimal.valueOf(5000)) >= 0) {
flag5000Map.put(fid, 1);
}
prevAccSerialNum = data.getAccSerialNum();
}
}
Integer flag90 = flag90Days.get(fid);
if (flag90 != null && flag90 == 1) {
iterator.remove();
break;
}
if (flag90 == null || flag90 == 0) {
if (data.getDaysPastDue() != null) {
boolean has90daysFlag = data.getDaysPastDue().compareTo(BigInteger.valueOf(90)) >= 0 && !data.getReportedDate().isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
iterator.remove();
break;
} else { } else {
flag90Days.put(fid, 0); return replacement;
iterator.remove();
} }
} else { }));
iterator.remove();
}
}
}
}
// если мы так и не нашли флаги для этих фидов - выставляем их в 0
flag5000Map.putIfAbsent(fid, 0);
flag90Days.putIfAbsent(fid, 0);
}
log.debug("Complete calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
} }
/** /**
* Выбирает данные из RU_PAYMT и ищет по ним флаги просрочек 90Days * Флаг = 1, если выполняется хотя бы одно из условий:
* * В INDIC.RU_PASTDUEARREAR или RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0. * Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
* @param fids - отсортирован по возрастанию фидов
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/ */
private void findFlagsRuPaymt(List<Integer> fids, Map<Integer, Integer> flag90Days) { private Map<Integer, Integer> calcFlag90DaysByPastDueArrear(List<Integer> fids) {
log.debug("Start calculating flag 90 days by RU_PAPAYMT");
LocalDate yearAgo = LocalDate.now().minusYears(1); LocalDate yearAgo = LocalDate.now().minusYears(1);
List<RuPaymtData> ruPaymtDataList = mapper.findRuPaymtData(fids, yearAgo); return mapper.findFlag90ByPastDueArrear(fids, yearAgo).stream()
ruPaymtDataList.sort(Comparator.comparing(RuPaymtData::getFid, Comparator.naturalOrder())); .collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
(existing, replacement) -> {
for (Integer fid : fids) { if (existing == 1) {
Iterator<RuPaymtData> iterator = ruPaymtDataList.iterator(); return existing;
while (iterator.hasNext()) {
RuPaymtData data = iterator.next();
if (fid.equals(data.getFid())) {
Integer flag90 = flag90Days.get(fid);
if (flag90 != null && flag90 == 1) {
iterator.remove();
break;
}
if (flag90 == null || flag90 == 0) {
if (data.getDaysPastDue() != null) {
boolean has90daysFlag = data.getDaysPastDue().compareTo(BigInteger.valueOf(90)) >= 0 && !data.getReportedDate().isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
iterator.remove();
break;
} else { } else {
flag90Days.put(fid, 0); return replacement;
iterator.remove();
} }
} else { }));
iterator.remove();
}
}
}
}
flag90Days.putIfAbsent(fid, 0);
}
log.debug("Complete calculating flag 90 days by RU_PAPAYMT");
} }
public Map<Integer, FlagsService.FlagsData> findFlags2(List<Integer> fids) { private Map<Integer, Integer> calcFlag90DaysByRuPaymt(List<Integer> fids) {
if (fids.isEmpty()) {
return new HashMap<>();
}
List<Integer> distinctFids = fids.stream()
.distinct()
.sorted(Comparator.naturalOrder())
.toList();
log.debug("Fids count: {}", distinctFids.size());
Map<Integer, Integer> flags5000 = new HashMap<>();
Map<Integer, Integer> flags90DaysByPastDueArrear = new HashMap<>();
Map<Integer, Integer> flags90DaysByRuPaymt = new HashMap<>();
findFlagsRuPastDueArrear2(distinctFids, flags5000, flags90DaysByPastDueArrear);
findFlagsRuPaymt2(distinctFids, flags90DaysByRuPaymt);
Map<Integer, FlagsService.FlagsData> result = new HashMap<>();
fids.forEach(fid -> {
Integer flagDebt5000 = flags5000.get(fid);
if (flagDebt5000 == null) {
flagDebt5000 = 0;
}
Integer flag90days = flags90DaysByPastDueArrear.get(fid);
if (flag90days == null || flag90days == 0) {
flag90days = flags90DaysByRuPaymt.get(fid);
if (flag90days == null) {
flag90days = 0;
}
}
result.put(fid, new FlagsService.FlagsData(flagDebt5000, flag90days));
});
return result;
}
/**
* Выбирает данные из RU_PASTDUEARREAR и ищет по ним флаги просрочек
* По FID из RU_PASTDUEARREAR выбираются все уникальные ACC_SERIAL_NUM с максимальной датой REPORTED_DT.
* Если по хотя бы одной такой записи AMT_PAST_DUE > 5000, то flag500 устанавливается в 1.
* Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0.
* <p>
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PASTDUEARREAR у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
*
* @param fids - отсортирован по возрастанию фидов
* @param flag5000Map - рещультирующая мапа флагов просрочек debt5000
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/
private void findFlagsRuPastDueArrear2(List<Integer> fids, Map<Integer, Integer> flag5000Map, Map<Integer, Integer> flag90Days) {
log.debug("Start calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("fids", fids);
jdbcTemplate.query(SELECT_RU_PASDUEARREAR_DATA, params, (ResultSet rs) -> {
Integer currentFid = null;
BigInteger prevAccSerialNum = null;
boolean processed5000ForCurrentId = false;
boolean processed90DaysForCurrentId = false;
LocalDate yearAgo = LocalDate.now().minusYears(1); LocalDate yearAgo = LocalDate.now().minusYears(1);
return mapper.findFlag90ByRuPaymt(fids, yearAgo).stream()
while (rs.next()) { .collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
Integer fid = rs.getInt("FID"); (existing, replacement) -> {
if (existing == 1) {
// Переход к новому fid. Сброс состояния return existing;
if (!fid.equals(currentFid)) {
currentFid = fid;
prevAccSerialNum = null;
processed5000ForCurrentId = false;
processed90DaysForCurrentId = false;
// Инициализируем значения по умолчанию
flag5000Map.putIfAbsent(fid, 0);
flag90Days.putIfAbsent(fid, 0);
}
// Если для текущего FID уже обработали оба флага, пропускаем остальные записи
if (processed5000ForCurrentId && processed90DaysForCurrentId) {
continue;
}
// Обработка флага 5000
if (!processed5000ForCurrentId && flag5000Map.get(fid) == 0) {
BigInteger currentAccSerialNum = getBigInteger(rs, "ACC_SERIAL_NUM");
BigDecimal amtPastDue = getBigDecimal(rs, "AMT_PAST_DUE");
if (prevAccSerialNum == null || !prevAccSerialNum.equals(currentAccSerialNum)) {
if (amtPastDue != null && amtPastDue.compareTo(BigDecimal.valueOf(5000)) >= 0) {
flag5000Map.put(fid, 1);
processed5000ForCurrentId = true;
}
prevAccSerialNum = currentAccSerialNum;
}
}
// Обработка флага 90 дней
if (!processed90DaysForCurrentId) {
Integer currentFlag90 = flag90Days.get(fid);
if (currentFlag90 != null && currentFlag90 == 1) {
processed90DaysForCurrentId = true;
continue;
}
if (currentFlag90 == null || currentFlag90 == 0) {
BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
LocalDate reportedDate = rs.getObject("REPORTED_DT", LocalDate.class);
if (days != null) {
boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
&& reportedDate != null && !reportedDate.isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
processed90DaysForCurrentId = true;
} else { } else {
flag90Days.put(fid, 0); return replacement;
}
} else {
flag90Days.put(fid, 0);
}
}
}
}
return null;
});
log.debug("Complete calculating flag 5000 and 90 days by RU_PASTDUEARREAR");
}
/**
* Выбирает данные из RU_PAYMT и ищет по ним флаги просрочек 90Days
* <p>
* flag90Days = 1, если выполняется хотя бы одно из условий:
* В INDIC.RU_PAYMT у данного FID есть запись с DAYS_PAST_DUE 90 и REPORTED_DT >= текущая дата 12 месяцев.
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
*
* @param fids - отсортирован по возрастанию фидов
* @param flag90Days - рещультирующая мапа флагов просрочек 90_days
*/
private void findFlagsRuPaymt2(List<Integer> fids, Map<Integer, Integer> flag90Days) {
log.debug("Start calculating flag 90 days by RU_PAPAYMT");
LocalDate yearAgo = LocalDate.now().minusYears(1);
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("fids", fids);
params.addValue("reportedDate", yearAgo);
jdbcTemplate.query(SELECT_RU_PAYMT_DATA, params, rs -> {
Integer currentFid = null;
boolean processed90DaysForCurrentId = false;
while (rs.next()) {
Integer fid = rs.getInt("FID");
// Переход к новому fid. Сброс состояния
if (!fid.equals(currentFid)) {
currentFid = fid;
processed90DaysForCurrentId = false;
// Инициализируем значения по умолчанию
flag90Days.putIfAbsent(fid, 0);
}
// Обработка флага 90 дней
if (!processed90DaysForCurrentId) {
Integer currentFlag90 = flag90Days.get(fid);
if (currentFlag90 != null && currentFlag90 == 1) {
processed90DaysForCurrentId = true;
continue;
}
if (currentFlag90 == null || currentFlag90 == 0) {
BigInteger days = getBigInteger(rs, "DAYS_PAST_DUE");
LocalDate date = rs.getObject("REPORTED_DT", LocalDate.class);
if (days != null) {
boolean has90daysFlag = days.compareTo(BigInteger.valueOf(90)) >= 0
&& date != null && !date.isBefore(yearAgo);
if (has90daysFlag) {
flag90Days.put(fid, 1);
processed90DaysForCurrentId = true;
} else {
flag90Days.put(fid, 0);
}
} else {
flag90Days.put(fid, 0);
}
}
}
}
return null;
});
log.debug("Complete calculating flag 90 days by RU_PAPAYMT");
}
private BigInteger getBigInteger(ResultSet rs, String columnName) {
try {
String value = rs.getString(columnName);
return value != null ? new BigInteger(value) : null;
} catch (SQLException e) {
return null;
}
}
private BigDecimal getBigDecimal(ResultSet rs, String columnName) {
try {
return rs.getBigDecimal(columnName);
} catch (SQLException e) {
return null;
} }
}));
} }
@Getter @Getter
@ -442,23 +107,8 @@ public class FlagsService {
@Getter @Getter
@Setter @Setter
@AllArgsConstructor public static class FlagFidData {
@NoArgsConstructor
public static class RuPastDuarearData {
private Integer fid; private Integer fid;
private BigInteger accSerialNum; private Integer flag;
private LocalDate reportedDate;
private BigDecimal amtPastDue;
private BigInteger daysPastDue;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class RuPaymtData {
private Integer fid;
private BigInteger daysPastDue;
private LocalDate reportedDate;
} }
} }

View file

@ -1,37 +0,0 @@
package ru.nbch.credit_tracker.service.indic;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.indic.PaymentsFlag;
import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
@Slf4j
@Service
public class FlagsService2 {
private final FlagsMapper flagsMapper;
public FlagsService2(FlagsMapper flagsMapper) {
this.flagsMapper = flagsMapper;
}
public Map<Integer, Integer> findFlagsDebt5000(List<Integer> fids) {
return flagsMapper.findFlagsDebt5000(fids).stream()
.collect(Collectors.toMap(PaymentsFlag::getFid, PaymentsFlag::getFlag));
}
public Map<Integer, Integer> findFlag90ByPastDueArrear(List<Integer> fids, LocalDate date) {
return flagsMapper.findFlag90ByPastDueArrear(fids, date).stream()
.collect(Collectors.toMap(PaymentsFlag::getFid, PaymentsFlag::getFlag));
}
public Map<Integer, Integer> findFlag90ByRuPaymt(List<Integer> fids, LocalDate date) {
return flagsMapper.findFlag90ByRuPaymt(fids, date).stream()
.collect(Collectors.toMap(PaymentsFlag::getFid, PaymentsFlag::getFlag));
}
}

View file

@ -1,11 +1,5 @@
package ru.nbch.credit_tracker.service.indic; package ru.nbch.credit_tracker.service.indic;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -13,8 +7,13 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.mapper.indic.PersonMapper; import ru.nbch.credit_tracker.mapper.indic.PersonMapper;
import ru.nbch.credit_tracker.model.IdData;
import ru.nbch.credit_tracker.model.SubjectProfile; import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@ -88,37 +87,6 @@ public class PersonService {
return resultMap; return resultMap;
} }
public void filledPassportsData(List<SubjectProfile> subjectProfiles) {
List<Integer> fids = subjectProfiles.stream().map(SubjectProfile::getFid).collect(Collectors.toList());
if (fids.isEmpty()) {
return;
}
List<PassportData> passports = mapper.findPassportData(fids);
Map<Integer, List<PassportData>> passportsMap = passports.stream()
.collect(Collectors.groupingBy(PassportData::getFid));
subjectProfiles.forEach(subjectProfile -> {
PassportData passport = findPassport(passportsMap.get(subjectProfile.getFid()));
if (passport != null) {
IdData idProfile = IdData.builder()
.serNum(passport.getSerNum())
.idNum(passport.getIdNum())
.issDate(passport.getIssDate())
.idType(passport.getIdType())
.idType2(passport.getIdType2())
.fileSinceDate(passport.getFileSinceDate())
.build();
subjectProfile.setIdData(idProfile);
}
});
}
private boolean isRequiredPassportDataPresent(PassportData passportData) {
boolean isDocTypeNotNull = StringUtils.isNotBlank(passportData.idType) || StringUtils.isNotBlank(passportData.idType2);
return StringUtils.isNotBlank(passportData.idNum) && isDocTypeNotNull;
}
private PassportData findPassport(List<PassportData> passports) { private PassportData findPassport(List<PassportData> passports) {
if (passports == null || passports.isEmpty()) { if (passports == null || passports.isEmpty()) {
return null; return null;

View file

@ -1,110 +0,0 @@
package ru.nbch.credit_tracker.service.indic;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.mapper.indic.PersonMapper;
import ru.nbch.credit_tracker.model.IdData;
import ru.nbch.credit_tracker.model.SubjectProfile;
@Slf4j
@Service
public class PersonServiceV2 {
private final PersonMapper mapper;
public PersonServiceV2(PersonMapper mapper) {
this.mapper = mapper;
}
public void filledPassportsData(List<SubjectProfile> subjectProfiles) {
List<Integer> fids = subjectProfiles.stream().map(SubjectProfile::getFid).collect(Collectors.toList());
if (fids.isEmpty()) {
return;
}
List<PersonService.PassportData> passports = mapper.findPassportData(fids);
Map<Integer, List<PersonService.PassportData>> passportsMap = passports.stream()
.collect(Collectors.groupingBy(PersonService.PassportData::getFid));
subjectProfiles.forEach(subjectProfile -> {
PersonService.PassportData passport = findPassport(passportsMap.get(subjectProfile.getFid()));
if (passport != null) {
IdData idProfile = IdData.builder()
.serNum(passport.getSerNum())
.idNum(passport.getIdNum())
.issDate(passport.getIssDate())
.idType(passport.getIdType())
.idType2(passport.getIdType2())
.fileSinceDate(passport.getFileSinceDate())
.build();
subjectProfile.setIdData(idProfile);
}
});
}
//todo подлежит рефакторингу
private PersonService.PassportData findPassport(List<PersonService.PassportData> passports) {
if (passports == null || passports.isEmpty()) {
return null;
}
List<PersonService.PassportData> documents = passports.stream()
.filter(this::isDUL)
.toList(); // берем только ДУЛ
if (documents.isEmpty()) {
return null;
}
// Отдаем приоритет паспортам РФ
PersonService.PassportData passport = documents.stream()
.filter(p -> "21".equals(p.getIdType2()) || "21".equals(p.getIdType()))
.sorted(
Comparator.comparing(
PersonService.PassportData::getIssDate,
Comparator.nullsLast(Comparator.reverseOrder())
).thenComparing(
PersonService.PassportData::getFileSinceDate,
Comparator.nullsLast(Comparator.reverseOrder())
)
).findFirst().orElse(null);
if (passport != null) {
return passport;
}
// Если паспорт РФ не найден, смотри остальные ДУЛ'ы и берем первый
passport = documents.stream()
.sorted(
Comparator.comparing(
PersonService.PassportData::getIssDate,
Comparator.nullsLast(Comparator.reverseOrder())
).thenComparing(
PersonService.PassportData::getFileSinceDate,
Comparator.nullsLast(Comparator.reverseOrder())
)
).findFirst().orElse(null);
return passport;
}
//todo подлежит рефакторингу;
private boolean isDUL(PersonService.PassportData p) {
Integer idType;
Double idType2;
try {
idType = p.getIdType() != null ? Integer.parseInt(p.getIdType()) : null;
} catch (NumberFormatException e) {
idType = null;
}
try {
idType2 = p.getIdType2() != null ? Double.valueOf(p.getIdType2()) : null;
} catch (NumberFormatException e) {
idType2 = null;
}
return (idType != null && idType <= 30 && idType != 12) || (idType2 != null && (idType2 >= 1 || idType2 <= 999));
}
}

View file

@ -1,269 +1,58 @@
package ru.nbch.credit_tracker.service.indic; package ru.nbch.credit_tracker.service.indic;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap;
import ru.nbch.credit_tracker.entities.indic.Name;
import ru.nbch.credit_tracker.repository.PhoneRepository;
@Slf4j
@Service @Service
@Deprecated
//todo оставил для временного подсмотреть на старую логику
public class PhoneService { public class PhoneService {
private final PhoneRepository phoneRepository; private final PhoneMapper mapper;
public PhoneService(PhoneRepository phoneRepository) { public PhoneService(PhoneMapper mapper) {
this.phoneRepository = phoneRepository; this.mapper = mapper;
} }
/** /**
* Ищет фиды по номерам телефона * Ищет фиды по номерам телефона
* * @return Map: key - номер телефона; value - fid
* @return Map: key - fid; value - {@link PhoneFidMap}
*/ */
public Map<Integer, PhoneFidMap> defineFidByPhone(List<PhoneFidMap> subjects) { public Map<String, Integer> findFidsPhoneData(List<PhoneFidMap> subjects) {
List<String> phones = subjects.stream(). List<String> phones = subjects.stream().map(subject -> "9" + subject.getPhone()).toList();
map(subject -> "9" + subject.getPhone()) return convertFidsDataToMap(mapper.findFids(phones));
.toList(); }
subjects.sort( private Map<String, Integer> convertFidsDataToMap(List<FidsData> fidsData) {
Comparator.comparing( if (fidsData == null || fidsData.isEmpty()) {
PhoneFidMap::getPhone, return new HashMap<>();
Comparator.naturalOrder() }
return fidsData.stream()
.collect(Collectors.groupingBy(
FidsData::getPhone,
Collectors.collectingAndThen(
Collectors.maxBy(
Comparator.comparing(FidsData::getFileSinceDate)
.thenComparing(FidsData::getFid)
),
data -> data.map(FidsData::getFid).orElse(null)
) )
); ));
List<PhoneRepository.FidsDataLong> phonesByFid = phoneRepository.findFidsLong(phones);
log.debug("Start merging fids");
Map<Integer, PhoneFidMap> phoneFidMap = merger(subjects, phonesByFid);
log.debug("End merging fids");
return phoneFidMap;
} }
private Map<Integer, PhoneFidMap> merger(List<PhoneFidMap> subjects, List<PhoneRepository.FidsDataLong> phonesByFid) { @Getter
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>(); @Setter
public static class FidsData {
if (phonesByFid.isEmpty()) { private Integer fid;
return phoneFidMap; private String phone;
} private LocalDate fileSinceDate;
int index = 0;
for (PhoneFidMap subject : subjects) {
Long normalizedPhone = Long.parseLong("9" + subject.getPhone());
while (index < phonesByFid.size()) {
PhoneRepository.FidsDataLong currentFidData = phonesByFid.get(index);
if (currentFidData.getPhone() < normalizedPhone) {
index++;
continue;
}
if (currentFidData.getPhone().equals(normalizedPhone)) {
phoneFidMap.put(currentFidData.getFid(), subject);
index++;
break;
}
break;
}
if (index >= phonesByFid.size()) {
break;
}
}
return phoneFidMap;
}
public void defineFidsAndNameByPhone2(List<PhoneFidMap> subjects) {
List<String> phones = subjects.stream().
map(subject -> "9" + subject.getPhone())
.toList();
subjects.sort(
Comparator.comparing(
PhoneFidMap::getPhone,
Comparator.naturalOrder()
)
);
List<PhoneRepository.FidsDataWithName> phonesByFid = phoneRepository.findFidsWithName(phones);
log.debug("Start merging fids and names");
Map<Integer, List<PhoneRepository.FidsDataWithName>> fidFidsDataMap = phonesByFid.stream()
.collect(Collectors.groupingBy(PhoneRepository.FidsDataWithName::getFid));
Map<String, Integer> phoneToFidsMap = new HashMap<>();
for (PhoneRepository.FidsDataWithName fidsData : phonesByFid) {
if (!phoneToFidsMap.containsKey(fidsData.getPhone())) {
phoneToFidsMap.put(fidsData.getPhone(), fidsData.getFid());
}
}
for (PhoneFidMap phoneFid : subjects) {
String phone = "9" + phoneFid.getPhone();
Integer fid = phoneToFidsMap.get(phone);
if (fid != null) {
phoneFid.setFid(fid);
List<PhoneRepository.FidsDataWithName> fidsDataWithNames = fidFidsDataMap.get(fid);
if (fidsDataWithNames != null) {
PhoneRepository.FidsDataWithName nameData = defineNameData(fidsDataWithNames);
if (nameData != null) {
phoneFid.setFirstName(nameData.getFirstName());
phoneFid.setMiddleName(nameData.getMiddleName());
phoneFid.setLastName(nameData.getLastName());
phoneFid.setBirthDate(nameData.getBirthDate());
phoneFid.setFileSinceDate(nameData.getFileSinceDateName());
}
}
phoneToFidsMap.remove(phone);
}
}
log.debug("End merging fids and names");
}
@Deprecated
private PhoneRepository.FidsDataWithName defineNameData(List<PhoneRepository.FidsDataWithName> dataList) {
PhoneRepository.FidsDataWithName currentData = null;
for (PhoneRepository.FidsDataWithName fidsData : dataList) {
if (currentData == null) {
currentData = fidsData;
continue;
}
LocalDate currentDate = currentData.getFileSinceDateName();
LocalDate newDate = fidsData.getFileSinceDateName();
if (currentDate != null && newDate != null) {
if (currentDate.isBefore(newDate)) {
currentData = fidsData;
}
} else if (currentDate == null) {
currentData = fidsData;
}
}
return currentData;
}
private Name defineNameDataV2(List<Name> dataList) {
Name currentData = null;
for (Name fidsData : dataList) {
if (currentData == null) {
currentData = fidsData;
continue;
}
LocalDate currentDate = currentData.getFileSinceDate();
LocalDate newDate = fidsData.getFileSinceDate();
if (currentDate != null && newDate != null) {
if (currentDate.isBefore(newDate)) {
currentData = fidsData;
}
} else if (currentDate == null) {
currentData = fidsData;
}
}
return currentData;
}
private Map<Integer, PhoneFidMap> merger2(List<PhoneFidMap> subjects, List<PhoneRepository.FidsData> phonesByFid) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
Iterator<PhoneRepository.FidsData> iterator = phonesByFid.iterator();
String lastUniquePhone = null;
PhoneRepository.FidsData fidsData = null;
for (PhoneFidMap phoneFid : subjects) {
String normalizedPhone = 9 + phoneFid.getPhone();
while (iterator.hasNext()) {
fidsData = iterator.next();
if (StringUtils.isNotEmpty(lastUniquePhone) && lastUniquePhone.equals(fidsData.getPhone())) {
iterator.remove();
continue;
}
if (fidsData.getPhone().equals(normalizedPhone) || normalizedPhone.equals(lastUniquePhone)) {
lastUniquePhone = fidsData.getPhone();
iterator.remove();
break;
}
lastUniquePhone = fidsData.getPhone();
break;
}
if (fidsData != null && normalizedPhone.equals(lastUniquePhone)) {
phoneFidMap.put(fidsData.getFid(), phoneFid);
}
}
return phoneFidMap;
}
/**
* Ищет фиды по номерам телефона. Обогащает {@link PhoneFidMap} для найденных фидов данными из Name и фидом.
* @return Map: key - FID, value - {@link PhoneFidMap}
*/
public Map<Integer, PhoneFidMap> defineFidsAndNameByPhone(List<PhoneFidMap> subjects) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
List<String> phones = subjects.stream().
map(subject -> "9" + subject.getPhone())
.toList();
subjects.sort(
Comparator.comparing(
PhoneFidMap::getPhone,
Comparator.naturalOrder()
)
);
List<PhoneRepository.FidsDataWithName> phonesByFid = phoneRepository.findFidsWithName(phones);
log.debug("Start merging fids and names");
Map<Integer, List<PhoneRepository.FidsDataWithName>> fidFidsDataMap = phonesByFid.stream()
.collect(Collectors.groupingBy(PhoneRepository.FidsDataWithName::getFid));
Map<String, Integer> phoneToFidsMap = new HashMap<>();
for (PhoneRepository.FidsDataWithName fidsData : phonesByFid) {
if (!phoneToFidsMap.containsKey(fidsData.getPhone())) {
phoneToFidsMap.put(fidsData.getPhone(), fidsData.getFid());
}
}
for (PhoneFidMap phoneFid : subjects) {
String phone = "9" + phoneFid.getPhone();
Integer fid = phoneToFidsMap.get(phone);
if (fid != null) {
phoneFid.setFid(fid);
List<PhoneRepository.FidsDataWithName> fidsDataWithNames = fidFidsDataMap.get(fid);
if (fidsDataWithNames != null) {
PhoneRepository.FidsDataWithName nameData = defineNameData(fidsDataWithNames);
if (nameData != null) {
phoneFid.setFirstName(nameData.getFirstName());
phoneFid.setMiddleName(nameData.getMiddleName());
phoneFid.setLastName(nameData.getLastName());
phoneFid.setBirthDate(nameData.getBirthDate());
phoneFid.setFileSinceDate(nameData.getFileSinceDateName());
}
fidFidsDataMap.remove(fid);
}
phoneFidMap.put(fid, phoneFid); // TODO можно перезатереть запись если у разных phone нашлись одинаковые фиды
phoneToFidsMap.remove(phone);
}
}
log.debug("End merging fids and names");
return phoneFidMap;
} }
} }

View file

@ -1,49 +0,0 @@
package ru.nbch.credit_tracker.service.indic;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.indic.Name;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import ru.nbch.credit_tracker.utils.CTUtil;
@Slf4j
@Service
public class PhoneService2 {
private final PhoneRepository phoneRepository;
private final Comparator<Name> byDateThenIdNullSafe =
Comparator.comparing(Name::getFileSinceDate, Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparing(Name::getFid);
public PhoneService2(PhoneRepository phoneRepository) {
this.phoneRepository = phoneRepository;
}
/**
* Поиск данных по таблицам PHONE_NORM и NAME по списку номеров телефонов
* @param subjects
* @return - отображение NAME к номеру телефону, отфильтрованному по логике
* FID с максимальным значением FILE_SINCE_DT. Если FILE_SINCE_DT оказался одинаковый, берется максимальный FID
*/
public Map<String, Name> findNameByNumber(List<SubjectProfile> subjects) {
List<String> phones = subjects.stream()
.map(subject -> CTUtil.normalizePhoneNumber(subject.getPhone()))
.toList();
List<Name> names = phoneRepository.findNameByPhones(phones);
return names.stream()
.collect(Collectors.toMap(
Name::getNumber,
Function.identity(),
BinaryOperator.maxBy(byDateThenIdNullSafe)
));
}
}

View file

@ -1,7 +1,7 @@
package ru.nbch.credit_tracker.service.signal; package ru.nbch.credit_tracker.service.signal;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.app.Hit; import ru.nbch.credit_tracker.entities.dto.Hit;
import ru.nbch.credit_tracker.mapper.signal.HitsMapper; import ru.nbch.credit_tracker.mapper.signal.HitsMapper;
import java.util.List; import java.util.List;

View file

@ -1,84 +1,33 @@
package ru.nbch.credit_tracker.service.signal; package ru.nbch.credit_tracker.service.signal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.indic.Name;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject; import ru.nbch.credit_tracker.entities.user_request.monitoring_request.Subject;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper; import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.utils.CTUtil; import java.util.List;
@Service @Service
public class PhoneFidMapService { public class PhoneFidMapService {
private final PhoneFidMapMapper mapper; private final PhoneFidMapMapper mapper;
private final SqlSessionFactory sqlSessionFactory;
public PhoneFidMapService(PhoneFidMapMapper mapper, public PhoneFidMapService(PhoneFidMapMapper mapper) {
@Qualifier("sqlSessionSignal") SqlSessionFactory sqlSessionFactory) {
this.mapper = mapper; this.mapper = mapper;
this.sqlSessionFactory = sqlSessionFactory;
} }
@Deprecated
public void registerSubjects(List<Subject> subjects, Long packageId) { public void registerSubjects(List<Subject> subjects, Long packageId) {
mapper.registerSubjects(subjects, packageId); mapper.registerSubjects(subjects, packageId);
} }
/**
* Регистрирует в базе список субъектов переданных в запросе
*
* @param subjects - список субъектов
* @param packageId - уникальный идентификатор пакета
*/
public void register(List<SubjectProfile> subjects, Map<String, Name> nameByPhone, Long packageId) {
List<PhoneFidMap> phoneFidMaps = subjects.stream().map(subjectProfile -> {
Integer fid = Optional
.ofNullable(nameByPhone.get(CTUtil.normalizePhoneNumber(subjectProfile.getPhone())))
.map(Name::getFid)
.orElse(null);
return PhoneFidMap.builder()
.phone(subjectProfile.getPhone())
.subjectId(subjectProfile.getIdentifier())
.packageId(packageId)
.fid(fid)
.build();
}
).toList();
mapper.register(phoneFidMaps);
}
public List<PhoneFidMap> findFullPhoneFidMap(Long packageId, int offset, int limit) { public List<PhoneFidMap> findFullPhoneFidMap(Long packageId, int offset, int limit) {
return mapper.findFullPhoneFidMap(packageId, offset, limit); return mapper.findFullPhoneFidMap(packageId, offset, limit);
} }
public void updatePhoneFidMaps(Collection<PhoneFidMap> subjects) { public void updatePhoneFidMaps(List<PhoneFidMap> subjects) {
mapper.updatePhoneFidMaps(subjects); mapper.updatePhoneFidMaps(subjects);
} }
public void updatePhoneFidMaps2(Collection<PhoneFidMap> subjects) {
final int batchSize = 1000;
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
PhoneFidMapMapper mapper = session.getMapper(PhoneFidMapMapper.class);
int i = 0;
for (PhoneFidMap s : subjects) {
mapper.updateOne(s);
if (++i % batchSize == 0) session.flushStatements();
}
session.flushStatements();
session.commit();
}
}
public void updatePhoneFidMapsErrors(List<PhoneFidMap> subjects) { public void updatePhoneFidMapsErrors(List<PhoneFidMap> subjects) {
mapper.updatePhoneFidMapsErrors(subjects); mapper.updatePhoneFidMapsErrors(subjects);
} }

View file

@ -70,10 +70,6 @@ public class SignalRestService {
*/ */
public boolean setupMonitoring(Long packageId) throws Exception { public boolean setupMonitoring(Long packageId) throws Exception {
String path = signalService.writeSignals(packageId); String path = signalService.writeSignals(packageId);
return setupMonitoring(path);
}
public boolean setupMonitoring(String path) throws Exception {
Resource request = compressFile(path); Resource request = compressFile(path);
MultiValueMap<String, Resource> body = new LinkedMultiValueMap<>(); MultiValueMap<String, Resource> body = new LinkedMultiValueMap<>();
@ -99,6 +95,8 @@ public class SignalRestService {
} else { } else {
log.error("неизвестная ошибка при постановке пакета на мониторинг в официальный сервис сигналов: {}", response.getBody()); log.error("неизвестная ошибка при постановке пакета на мониторинг в официальный сервис сигналов: {}", response.getBody());
} }
CTUtil.removeFile(path);
CTUtil.removeFile(path + ".gz");
return false; return false;
} }
return false; return false;

View file

@ -1,7 +1,7 @@
package ru.nbch.credit_tracker.service.signal; package ru.nbch.credit_tracker.service.signal;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.app.UserPackage; import ru.nbch.credit_tracker.entities.dto.UserPackage;
import ru.nbch.credit_tracker.enums.Status; import ru.nbch.credit_tracker.enums.Status;
import ru.nbch.credit_tracker.mapper.signal.UserPackageMapper; import ru.nbch.credit_tracker.mapper.signal.UserPackageMapper;

View file

@ -1,6 +0,0 @@
package ru.nbch.credit_tracker.service.task;
public interface Handler<X, Z> {
Z handle(X taskPayload);
default int preferredConcurrency() {return 10;}
}

View file

@ -1,41 +0,0 @@
package ru.nbch.credit_tracker.service.task;
import java.util.List;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineTerminator;
public class Pipeline {
private Runnable starter;
private IPipeLineTerminator<?,?> terminator;
private List<Stage<?, ?>> stages;
public Pipeline() {
}
public Stage<?, ?> getLast() {
return stages.getLast();
}
public Runnable getStarter() {
return starter;
}
public void setStarter(Runnable starter) {
this.starter = starter;
}
public IPipeLineTerminator<?, ?> getTerminator() {
return terminator;
}
public void setTerminator(IPipeLineTerminator<?, ?> terminator) {
this.terminator = terminator;
}
public List<Stage<?, ?>> getStages() {
return stages;
}
public void setStages(List<Stage<?, ?>> stages) {
this.stages = stages;
}
}

View file

@ -1,117 +0,0 @@
package ru.nbch.credit_tracker.service.task;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.config.task.StageQueueFactory;
import ru.nbch.credit_tracker.model.impl.PartFilePayload;
import ru.nbch.credit_tracker.model.impl.SignalXmlFilePayload;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.signal.SignalRestService;
import ru.nbch.credit_tracker.service.task.impl.FidIdEnricher;
import ru.nbch.credit_tracker.service.task.impl.FlagsEnricherV2;
import ru.nbch.credit_tracker.service.task.impl.SaveSubjectHandler;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineStarter;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineTerminator;
import ru.nbch.credit_tracker.service.task.setup.PipeLineBuilderV2;
import ru.nbch.credit_tracker.utils.LogUtil;
import ru.nbch.credit_tracker.utils.error.ExceptionUtils;
@Component
@Slf4j
public class PipelinePackageManager {
private final SignalRestService signalRestService;
private final FlagsEnricherV2 flagsEnricherV2;
private final StageQueueFactory<SubjectPayload> saveSubjectQueueFactory;
private final StageQueueFactory<SubjectPayload> idEnricherQueueFactory;
private final StageQueueFactory<PartFilePayload> sendSignalQueueFactory;
private final SaveSubjectHandler saveSubjectHandler;
private final FidIdEnricher fidIdEnricherHandler;
private final Supplier<IPipeLineTerminator<PartFilePayload, SignalXmlFilePayload>> pipelineTerminatorFactory;
private final BiFunction<Long, String, IPipeLineStarter<SubjectPayload>> pipelineStarterFactory;
private final CreditTrackerSettings creditTrackerSettings;
private final ExecutorService executorService = Executors.newFixedThreadPool(10, r -> {
Thread t = new Thread(r);
t.setName("pipe-starter-%d".formatted(t.threadId()));
return t;
});
private final Semaphore semaphore;
private final static int MAX_PARALLEL_REQUESTS = 3;
public PipelinePackageManager(SignalRestService signalRestService,
@Qualifier("saveSubjectQueueFactory") StageQueueFactory<SubjectPayload> saveSubjectQueueFactory,
@Qualifier("idEnricherQueueFactory") StageQueueFactory<SubjectPayload> idEnricherQueueFactory,
@Qualifier("partFileQueueFactory") StageQueueFactory<PartFilePayload> sendSignalQueueFactory,
SaveSubjectHandler saveSubjectHandler, FidIdEnricher fidIdEnricherHandler,
Supplier<IPipeLineTerminator<PartFilePayload, SignalXmlFilePayload>> pipelineTerminatorFactory,
BiFunction<Long, String, IPipeLineStarter<SubjectPayload>> pipelineStarterFactory,
FlagsEnricherV2 flagsEnricherV2,
CreditTrackerSettings creditTrackerSettings) {
this.signalRestService = signalRestService;
this.saveSubjectQueueFactory = saveSubjectQueueFactory;
this.idEnricherQueueFactory = idEnricherQueueFactory;
this.sendSignalQueueFactory = sendSignalQueueFactory;
this.saveSubjectHandler = saveSubjectHandler;
this.fidIdEnricherHandler = fidIdEnricherHandler;
this.pipelineTerminatorFactory = pipelineTerminatorFactory;
this.pipelineStarterFactory = pipelineStarterFactory;
this.flagsEnricherV2 = flagsEnricherV2;
this.creditTrackerSettings = creditTrackerSettings;
this.semaphore = new Semaphore(MAX_PARALLEL_REQUESTS);
}
public void buildAndRun(Long packageId, String pkgPath) throws Exception {
if (!semaphore.tryAcquire()) {
throw new RuntimeException("Too many concurrent requests");
}
log.info("throttler permits after acquire: {}", semaphore.availablePermits());
try {
Pipeline pipeline = PipeLineBuilderV2.first(saveSubjectQueueFactory.getObject(), idEnricherQueueFactory.getObject())
.handler(saveSubjectHandler)
.poison(SubjectPayload.POISON)
.handlerResult(flagsEnricherV2)
.andThen()
.stage(sendSignalQueueFactory.getObject())
.handler(fidIdEnricherHandler)
.poison(PartFilePayload.POISON)
.andThen()
.fillInitialQueue(pipelineStarterFactory.apply(packageId, pkgPath))
.pollFinishingQueue(pipelineTerminatorFactory.get())
.build();
executorService.submit(LogUtil.wrap(LogUtil.extractMdcMap(), pipeline.getStarter()));
executorService.submit(LogUtil.wrap(LogUtil.extractMdcMap(), pipeline.getTerminator()));
pipeline.getTerminator()
.doneFuture()
.orTimeout(creditTrackerSettings.getTimeout(), TimeUnit.MINUTES)
.whenComplete((v, e) -> {
try {
if (e != null) {
log.error(ExceptionUtils.getStackTrace(e));
return;
}
SignalXmlFilePayload res = (SignalXmlFilePayload) v;
log.trace("Signal file by path: {} is completed", res.getSignalXmlFilePath());
// signalRestService.setupMonitoring(res.getSignalXmlFilePath().toString());
} catch (Exception ex) {
log.error("pipeline finished with error{}", ExceptionUtils.getStackTrace(ex));
} finally {
log.info("packageId={}: pipeline finished", packageId);
pipeline.getStages().forEach(Stage::close);
semaphore.release();
}
});
} catch (Exception e) {
semaphore.release();
throw e;
}
}
}

View file

@ -1,174 +0,0 @@
package ru.nbch.credit_tracker.service.task;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.utils.LogUtil;
@Slf4j
public class Stage<X extends StagePayload, Z extends StagePayload> implements Runnable {
private final Handler<X, Z> dispatcher;
private final BlockingQueue<X> in;
private final BlockingQueue<Z> out;
private final Z POISON;
private final ExecutorService ioPool;
private Consumer<Z> consumer = null;
private final String dispatcherName;
private static final ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy
= new ThreadPoolExecutor.CallerRunsPolicy();
public Stage(Handler<X, Z> dispatcher,
BlockingQueue<X> in,
BlockingQueue<Z> out,
Z POISON) {
this.dispatcher = dispatcher;
this.in = in;
this.out = out;
this.dispatcherName = dispatcher.getClass().getSimpleName();
var mdcMap = LogUtil.extractMdcMap();
this.ioPool = new ThreadPoolExecutor(dispatcher.preferredConcurrency(),
dispatcher.preferredConcurrency(),
0L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1),
r -> {
Thread t = new Thread(LogUtil.wrap(mdcMap, r));
t.setName("pipe-handler-%s-%d".formatted(dispatcherName, t.threadId()));
return t;
},
(r, executor) -> {
log.info("stage {}: all threads are working; switch to CallerRunsPolicy", dispatcherName);
callerRunsPolicy.rejectedExecution(r, executor);
}
);
this.POISON = POISON;
}
public void startRunning() {
ioPool.submit(this);
}
@Override
public void run() {
List<Future<Z>> inflight = new ArrayList<>();
try {
int counter = 0;
while (true) {
X b = takeQuiet(in, Duration.ofSeconds(5));
if (wasInterrupted()) {
break;
}
if (b == null) continue;
if (b.isPoison()) {
log.info("Got poison");
// дождаться всех in-flight задач
for (Future<Z> f : inflight) {
if (wasInterrupted()) {
log.info("stage {} was interrupted while waiting for tasks to finish",
dispatcher.getClass().getSimpleName());
break;
}
f.get();
}
putQuiet(out, POISON);
log.info("stage {} finished.", dispatcher.getClass().getSimpleName());
return;
}
counter++;
int finalCounter = counter;
Future<Z> f = ioPool.submit(() -> {
try {
if (wasInterrupted()) {
log.info("stage {} task {} interrupted...", dispatcher.getClass().getSimpleName(), finalCounter);
return null;
}
log.info("stage {} task {} started.", dispatcher.getClass().getSimpleName(), finalCounter);
Z p = dispatcher.handle(b);
putQuiet(out, p);
log.info("stage {} task {} ended.", dispatcher.getClass().getSimpleName(), finalCounter);
return p;
} catch (Exception e) {
log.error("{} ERROR: {}", dispatcherName, ExceptionUtils.getStackTrace(e));
return null;
}
}
);
inflight.add(f);
if ((counter % 20) == 0) {
inflight.removeIf(Future::isDone);
}
}
} catch (Exception e) {
log.error("Error for {}: {}; {}", dispatcherName, info(), ExceptionUtils.getStackTrace(e));
putQuiet(out, POISON);
}
}
private String info() {
ThreadPoolExecutor casted = (ThreadPoolExecutor) ioPool;
return "executor active tasks: %d; queue size: %d, completed tasks: %d".formatted(
casted.getActiveCount(),
casted.getQueue().size(),
casted.getCompletedTaskCount()
);
}
private boolean wasInterrupted() {
if (Thread.currentThread().isInterrupted()) {
log.warn("pipeline was interrupted...");
putQuiet(out, POISON);
return true;
}
return false;
}
public void close() {
log.info("Stage {} is getting shutdown", dispatcher.getClass().getSimpleName());
ioPool.shutdownNow(); // прерываем обработчики
}
void putQuiet(BlockingQueue<Z> q, Z b) {
try {
if (consumer != null) {
consumer.accept(b);
}
q.put(b);
} catch (InterruptedException e) {
log.error("Error for {}: {}; {}", dispatcherName, info(), ExceptionUtils.getStackTrace(e));
Thread.currentThread().interrupt();
}
}
X takeQuiet(BlockingQueue<X> q, Duration timeout) {
try {
return q.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Error for {}: {}; {}", dispatcherName, info(), ExceptionUtils.getStackTrace(e));
Thread.currentThread().interrupt();
return null;
}
}
public BlockingQueue<X> getIn() {
return in;
}
public Consumer<Z> getConsumer() {
return consumer;
}
public void setConsumer(Consumer<Z> consumer) {
this.consumer = consumer;
}
}

View file

@ -1,82 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.builder.PersonFactoryBuilderV2;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.external_request.signals.Person;
import ru.nbch.credit_tracker.model.impl.PartFilePayload;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.indic.PersonServiceV2;
import ru.nbch.credit_tracker.service.task.Handler;
import ru.nbch.credit_tracker.service.xml.stax.SignalsStaxXmlWriterV2;
@Service
@Slf4j
public class FidIdEnricher implements Handler<SubjectPayload, PartFilePayload> {
private final PersonServiceV2 personService;
private final CreditTrackerSettings config;
public FidIdEnricher(PersonServiceV2 personService,
CreditTrackerSettings config) {
this.personService = personService;
this.config = config;
}
@Override
public PartFilePayload handle(SubjectPayload taskPayload) {
List<SubjectProfile> subjectsProfile = taskPayload.getSubjects();
log.debug("FidIdEnricher handle by packageId: {}; size: {}", taskPayload.getPackageId(), subjectsProfile.size());
personService.filledPassportsData(subjectsProfile);
List<Person> personOnProcessing = subjectsProfile.stream()
.filter(subjectProfile -> subjectProfile.getIdData() != null && subjectProfile.getNameData() != null)
.map(subjectProfile -> PersonFactoryBuilderV2.builder().personData(subjectProfile).build())
.toList();
log.debug("personOnProcessing size: {}", personOnProcessing.size());
Long packageId = taskPayload.getPackageId();
Path partPath = writeSignals(personOnProcessing, packageId);
return new PartFilePayload(partPath, packageId);
}
private Path writeSignals(List<Person> persons, Long packageId) {
Path dir = Paths.get(config.getLoadDir(), "tmp_folder");
try {
Files.createDirectories(dir);
} catch (IOException e) {
throw new RuntimeException(e);
}
String base = UUID.randomUUID() + "_output.xml";
log.debug("Write tmp xml by packageId: {}", packageId);
Path tmp = dir.resolve(base + ".part");
Path done = dir.resolve(base);
try (OutputStream os = Files.newOutputStream(tmp, StandardOpenOption.CREATE_NEW)) {
SignalsStaxXmlWriterV2 writer = new SignalsStaxXmlWriterV2(persons, null, config.getEncoding());
writer.writeWithStax(os); // перегрузи на OutputStream, если нужно
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
Files.move(tmp, done, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException e) {
throw new RuntimeException(e);
}
return done;
// try {
// mergerQueue.put(new PartFilePayload(done, packageId, null));
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
}
}

View file

@ -1,139 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.indic.FlagsService2;
@Slf4j
public class FlagsEnricher implements Runnable {
private final FlagsService2 flagsService;
private final PhoneFidMapMapper phoneFidMapMapper;
private BlockingQueue<SubjectPayload> in;
private final CompletableFuture<Void> done = new CompletableFuture<>();
private final ExecutorService ioPool;
private final ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
public FlagsEnricher(FlagsService2 flagsService,
PhoneFidMapMapper phoneFidMapMapper) {
this.flagsService = flagsService;
this.phoneFidMapMapper = phoneFidMapMapper;
this.ioPool = new ThreadPoolExecutor(10,
10,
0L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(),
r -> {
Thread t = new Thread(r);
t.setName("queue-handler-%s-%d".formatted(this.getClass().getSimpleName(), t.threadId()));
return t;
}
);
}
@Override
public void run() {
List<Future<?>> inFlight = new ArrayList<>();
while (!Thread.currentThread().isInterrupted()) {
try {
SubjectPayload subjectPayload = in.poll(5, TimeUnit.SECONDS);
if (subjectPayload == null) {
continue;
}
if (subjectPayload.isPoison()) {
log.debug("Got poison");
for (Future<?> f : inFlight) {
f.get();
}
done.complete(null);
return;
}
inFlight.add(ioPool.submit(() -> process(subjectPayload)));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("Error: {}", ExceptionUtils.getStackTrace(e));
}
}
}
public void process(SubjectPayload subjectPayload) {
List<SubjectProfile> subjects = subjectPayload.getSubjects();
log.debug("FlagsEnricher handle by packageId: {}, size: {}", subjectPayload.getPackageId(), subjects.size());
List<Integer> fids = subjects.stream()
.map(SubjectProfile::getFid)
.filter(Objects::nonNull)
.toList();
LocalDate yearAgo = LocalDate.now().minusYears(1);
Map<Integer, Integer> flagsDebt5000 = flagsService.findFlagsDebt5000(fids);
Map<Integer, Integer> flag90ByPastDueArrear = flagsService.findFlag90ByPastDueArrear(fids, yearAgo);
Map<Integer, Integer> flag90ByRuPaymt = flagsService.findFlag90ByRuPaymt(fids, yearAgo);
String json;
try {
json = mapper.writeValueAsString(subjects.stream().map(subject -> {
Integer debt5000 = null;
Integer flagDays90 = null;
LocalDateTime calculatedAt = null;
if (subject.getFid() != null) {
debt5000 = flagsDebt5000.getOrDefault(subject.getFid(), 0);
int pastDue90 = flag90ByPastDueArrear.getOrDefault(subject.getFid(), 0);
int paymt90 = flag90ByRuPaymt.getOrDefault(subject.getFid(), 0);
flagDays90 = Math.max(pastDue90, paymt90);
calculatedAt = LocalDateTime.now();
}
Integer finalDebt500 = debt5000;
Integer finalFlagDays9 = flagDays90;
LocalDateTime finalCalculatedAt = calculatedAt;
return new HashMap<String, Object>() {{
put("subject_id", subject.getIdentifier());
put("fid", subject.getFid());
put("phone", subject.getPhone());
put("package_id", subjectPayload.getPackageId());
put("flag_debt5000", finalDebt500);
put("flag_90_12", finalFlagDays9);
put("calculated_at", finalCalculatedAt);
}};
}).toList());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
phoneFidMapMapper.registerJson(json);
}
public void setPollingQueue(BlockingQueue<SubjectPayload> queue) {
this.in = queue;
}
public CompletableFuture<Void> doneFuture() {
return done;
}
}

View file

@ -1,111 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.indic.FlagsService2;
@Slf4j
@Service
public class FlagsEnricherV2 implements Consumer<SubjectPayload> {
private final FlagsService2 flagsService;
private final PhoneFidMapMapper phoneFidMapMapper;
private final ExecutorService ioPool;
private final ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
public FlagsEnricherV2(FlagsService2 flagsService,
PhoneFidMapMapper phoneFidMapMapper) {
this.flagsService = flagsService;
this.phoneFidMapMapper = phoneFidMapMapper;
this.ioPool = new ThreadPoolExecutor(10,
10,
0L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(100),
r -> {
Thread t = new Thread(r);
t.setName("flags-enricher-%d".formatted(t.threadId()));
return t;
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
public void process(SubjectPayload subjectPayload) {
List<SubjectProfile> subjects = subjectPayload.getSubjects();
log.debug("FlagsEnricher handle by packageId: {}, size: {}", subjectPayload.getPackageId(), subjects.size());
List<Integer> fids = subjects.stream()
.map(SubjectProfile::getFid)
.filter(Objects::nonNull)
.toList();
LocalDate yearAgo = LocalDate.now().minusYears(1);
Map<Integer, Integer> flagsDebt5000 = flagsService.findFlagsDebt5000(fids);
Map<Integer, Integer> flag90ByPastDueArrear = flagsService.findFlag90ByPastDueArrear(fids, yearAgo);
Map<Integer, Integer> flag90ByRuPaymt = flagsService.findFlag90ByRuPaymt(fids, yearAgo);
String json;
try {
json = mapper.writeValueAsString(subjects.stream().map(subject -> {
Integer debt5000 = null;
Integer flagDays90 = null;
LocalDateTime calculatedAt = null;
if (subject.getFid() != null) {
debt5000 = flagsDebt5000.getOrDefault(subject.getFid(), 0);
int pastDue90 = flag90ByPastDueArrear.getOrDefault(subject.getFid(), 0);
int paymt90 = flag90ByRuPaymt.getOrDefault(subject.getFid(), 0);
flagDays90 = Math.max(pastDue90, paymt90);
calculatedAt = LocalDateTime.now();
}
Integer finalDebt500 = debt5000;
Integer finalFlagDays9 = flagDays90;
LocalDateTime finalCalculatedAt = calculatedAt;
return new HashMap<String, Object>() {{
put("subject_id", subject.getIdentifier());
put("fid", subject.getFid());
put("phone", subject.getPhone());
put("package_id", subjectPayload.getPackageId());
put("flag_debt5000", finalDebt500);
put("flag_90_12", finalFlagDays9);
put("calculated_at", finalCalculatedAt);
}};
}).toList());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
phoneFidMapMapper.registerJson(json);
}
@Override
public void accept(SubjectPayload subjectPayload) {
ThreadPoolExecutor casted = (ThreadPoolExecutor) ioPool;
log.info("submitting payload. active tasks number: {}; queue size: {}", casted.getActiveCount(), casted.getQueue().size());
this.ioPool.submit(() -> {
process(subjectPayload);
});
}
}

View file

@ -1,78 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.user_request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineStarter;
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
import ru.nbch.credit_tracker.utils.error.ExceptionUtils;
public class KickPipeLineImpl implements IPipeLineStarter<SubjectPayload> {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Long packageId;
private final String pkgPath;
private final StaxXmlProcessor xmlProcessor;
private final int BATCH_SIZE;
public KickPipeLineImpl(StaxXmlProcessor xmlProcessor, Long packageId, String pkgPath,
CreditTrackerSettings creditTrackerSettings) {
this.packageId = packageId;
this.pkgPath = pkgPath;
this.xmlProcessor = xmlProcessor;
this.BATCH_SIZE = creditTrackerSettings.getBatchSize();
}
@Override
public void startFillingQueue(BlockingQueue<SubjectPayload> firstQueue) {
log.info("Start registering subjects. PackageId: {}", packageId);
AtomicInteger counter = new AtomicInteger(0);
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Path.of(pkgPath)), 8 * 1024 * 1024)) {
xmlProcessor.read(in, MonitoringRequest.class,
request -> {
if (!request.getSubjects().getSubject().isEmpty()) {
List<SubjectProfile> subjectProfileBatch = request.getSubjects().getSubject().stream().map(subject ->
SubjectProfile.builder()
.identifier(subject.getId())
.phone(subject.getClientId())
.build()
).collect(Collectors.toList());
int count = counter.incrementAndGet();
log.trace("Reading subject's {} batch. Total count: {}. PackageId: {}", count, request.getSubjects().getSubject().size(), packageId);
try {
firstQueue.put(new SubjectPayload(packageId, subjectProfileBatch));
} catch (InterruptedException e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
throw new RuntimeException(e);
}
request.getSubjects().getSubject().clear();
}
},
request -> request.getSubjects().getSubject().isEmpty()
,
BATCH_SIZE, "/MonitoringRequest/Subjects/Subject");
} catch (IOException e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
} finally {
log.debug("Send poison pill");
try {
firstQueue.put(new SubjectPayload(true));
} catch (InterruptedException e) {
log.error("couldn't send poison: {}", ExceptionUtils.getStackTrace(e));
}
}
}
}

View file

@ -1,59 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.indic.Name;
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
import ru.nbch.credit_tracker.model.NameData;
import ru.nbch.credit_tracker.model.SubjectProfile;
import ru.nbch.credit_tracker.model.impl.SubjectPayload;
import ru.nbch.credit_tracker.service.indic.PhoneService2;
import ru.nbch.credit_tracker.service.task.Handler;
import ru.nbch.credit_tracker.utils.CTUtil;
/**
* Стадия для сопоставления FID к номеру телефона и регистрации пакета в базе данных.
* Так же выполняется поиск и обогащение перс.данными из NAME для дальнейшей обработки.
*/
@Service
@Slf4j
//todo опять нейминг класса, варианты: ResolveSubject; DefineIcrsSubject; IcrsMapping/Resolving; PackageSaver; Filler...
public class SaveSubjectHandler implements Handler<SubjectPayload, SubjectPayload> {
private final PhoneService2 phoneService2;
private final PhoneFidMapMapper phoneFidMapMapper;
public SaveSubjectHandler(PhoneService2 phoneService2,
PhoneFidMapMapper phoneFidMapMapper) {
this.phoneService2 = phoneService2;
this.phoneFidMapMapper = phoneFidMapMapper;
}
@Override
public SubjectPayload handle(SubjectPayload taskPayload) {
List<SubjectProfile> subjects = taskPayload.getSubjects();
Long packageId = taskPayload.getPackageId();
log.debug("SaveSubjectStage handle by packageId: {}, size of bucket for save: {}", taskPayload.getPackageId(), subjects.size());
Map<String, Name> nameByPhone = phoneService2.findNameByNumber(subjects);
//todo сделать один проход для установки FID для вставки в PHONE_FID_MAP и одновременного заполнения инфо из Name для дальнейшей обработки
subjects.forEach(subject -> {
Name name = nameByPhone.get(CTUtil.normalizePhoneNumber(subject.getPhone()));
if (name != null) {
NameData nameData = NameData.builder()
.middleName(name.getMiddle())
.firstName(name.getFirst())
.lastName(name.getLast())
.fileSinceDate(name.getFileSinceDate())
.birthDate(name.getBirthDate())
.build();
subject.setFid(name.getFid());
subject.setNameData(nameData);
}
});
//todo стоит ли делать имутабл для subject?
return new SubjectPayload(taskPayload.getPackageId(), subjects);
}
}

View file

@ -1,148 +0,0 @@
package ru.nbch.credit_tracker.service.task.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.model.impl.PartFilePayload;
import ru.nbch.credit_tracker.model.impl.SignalXmlFilePayload;
import ru.nbch.credit_tracker.service.task.setup.IPipeLineTerminator;
@Slf4j
public final class XmlMergeCoordinator implements IPipeLineTerminator<PartFilePayload, SignalXmlFilePayload> {
private final Path mergedDir;
private final Charset encoding = Charset.forName("windows-1251");
private final int batchSize = 10;
private final CompletableFuture<SignalXmlFilePayload> done = new CompletableFuture<>();
private Path out;
private BlockingQueue<PartFilePayload> inQueue;
public XmlMergeCoordinator(CreditTrackerSettings config) {
this.mergedDir = Path.of(config.getLoadDir() + File.separator + "tmp_folder" + File.separator + "merged" + File.separator);
}
@Override
public void run() {
try {
Files.createDirectories(mergedDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (!Thread.currentThread().isInterrupted()) {
try {
PartFilePayload part = inQueue.poll(5, TimeUnit.SECONDS);
if (part == null) {
continue;
}
if (part.isPoison()) {
log.debug("Got poison");
done.complete(new SignalXmlFilePayload(out));
return;
}
mergeBatch(Collections.singletonList(part));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("Error: {}", ExceptionUtils.getStackTrace(e));
}
}
}
private void mergeBatch(List<PartFilePayload> inputs) throws Exception {
log.debug("Starting merge batch; size: {}", inputs.size());
Long packageId = inputs.stream().findFirst().get().getPackageId();
String outName = "merged_" + packageId + ".xml";
if (out == null) {
out = mergedDir.resolve(outName);
}
XMLOutputFactory xof = XMLOutputFactory.newFactory();
try (OutputStream os = Files.newOutputStream(out, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
XMLStreamWriter w = xof.createXMLStreamWriter(os, encoding.name());
for (PartFilePayload in : inputs) {
log.debug("Copy person from file {}", in.getPartPath());
copyPersonsChildren(in.getPartPath(), w);
}
w.flush();
w.close();
}
for (PartFilePayload p : inputs) Files.deleteIfExists(p.getPartPath());
// inputs.clear();
}
/**
* Копирует всё содержимое внутри <Persons>...</Persons> входного файла
* в текущий writer, но без повторного открытия/закрытия корневого тега.
*/
private void copyPersonsChildren(Path input, XMLStreamWriter w) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
try (InputStream is = Files.newInputStream(input)) {
XMLStreamReader r = xif.createXMLStreamReader(is, encoding.name());
while (r.hasNext()) {
int t = r.next();
if (t == XMLStreamConstants.START_ELEMENT && "Persons".equals(r.getLocalName())) {
break;
}
}
int depth = 0; // 0 внутри корневого контейнера
while (r.hasNext()) {
int t = r.next();
if (t == XMLStreamConstants.START_ELEMENT) {
depth++;
writeStart(w, r);
} else if (t == XMLStreamConstants.CHARACTERS) {
if (!r.isWhiteSpace()) w.writeCharacters(r.getText());
} else if (t == XMLStreamConstants.CDATA) {
w.writeCData(r.getText());
} else if (t == XMLStreamConstants.END_ELEMENT) {
if (depth > 0) {
w.writeEndElement();
depth--;
} else if ("Persons".equals(r.getLocalName())) {
break;
}
}
}
r.close();
}
}
private void writeStart(XMLStreamWriter w, XMLStreamReader r) throws XMLStreamException {
w.writeStartElement(r.getLocalName());
for (int i = 0; i < r.getAttributeCount(); i++) {
w.writeAttribute(r.getAttributeLocalName(i), r.getAttributeValue(i));
}
}
@Override
public void setPollingQueue(BlockingQueue<PartFilePayload> mergerQueue) {
this.inQueue = mergerQueue;
}
@Override
public CompletableFuture<SignalXmlFilePayload> doneFuture() {
return done;
}
}

View file

@ -1,8 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup;
import java.util.concurrent.BlockingQueue;
import ru.nbch.credit_tracker.model.StagePayload;
public interface IPipeLineStarter<Q extends StagePayload> {
void startFillingQueue(BlockingQueue<Q> queue);
}

View file

@ -1,11 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.model.TerminatePayload;
public interface IPipeLineTerminator<Q extends StagePayload, X extends TerminatePayload> extends Runnable {
void setPollingQueue(BlockingQueue<Q> queue);
CompletableFuture<X> doneFuture();
}

View file

@ -1,77 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.service.task.Pipeline;
import ru.nbch.credit_tracker.service.task.Stage;
import ru.nbch.credit_tracker.service.task.setup.step.HandlerStep;
public class PipeLineBuilderV2<FIRST extends StagePayload, LAST extends StagePayload> {
private final List<Stage<?, ?>> stages;
private Stage<FIRST, ?> firstStage;
private BlockingQueue<LAST> lastQueue;
private IPipeLineStarter<FIRST> pipeLineStarter;
private IPipeLineTerminator<LAST, ?> pipeLineTerminator;
private PipeLineBuilderV2() {
this.stages = new ArrayList<>();
}
public static <I extends StagePayload, O extends StagePayload>
HandlerStep<I, I, O> first(BlockingQueue<I> inputQueue, BlockingQueue<O> outputQueue) {
PipeLineBuilderV2<I, O> builder = new PipeLineBuilderV2<>();
builder.lastQueue = outputQueue;
HandlerStep<I, I, O> step = StageBuilderV2.create(inputQueue, outputQueue);
((StageBuilderV2<I, I, O>) step).setParent(builder);
return step;
}
@SuppressWarnings("unchecked")
public <NEXT extends StagePayload>
HandlerStep<FIRST, LAST, NEXT> stage(BlockingQueue<NEXT> outputQueue) {
HandlerStep<FIRST, LAST, NEXT> step = StageBuilderV2.create(lastQueue, outputQueue);
lastQueue = (BlockingQueue<LAST>) outputQueue;
((StageBuilderV2<FIRST, LAST, NEXT>) step).setParent((PipeLineBuilderV2<FIRST, NEXT>) this);
return step;
}
public PipeLineBuilderV2<FIRST, LAST> fillInitialQueue(IPipeLineStarter<FIRST> pipeLineStarter) {
Objects.requireNonNull(pipeLineStarter, "pipeLineStarter is required");
if (firstStage == null) {
throw new IllegalStateException("Pipeline has no stages");
}
this.pipeLineStarter = pipeLineStarter;
return this;
}
public PipeLineBuilderV2<FIRST, LAST> pollFinishingQueue(IPipeLineTerminator<LAST, ?> pipeLineTerminator) {
Objects.requireNonNull(pipeLineTerminator, "pipeLineTerminator is required");
this.pipeLineTerminator = pipeLineTerminator;
return this;
}
public Pipeline build() {
Pipeline p = new Pipeline();
p.setStarter(() -> {
stages.forEach(Stage::startRunning);
pipeLineStarter.startFillingQueue(firstStage.getIn());
});
pipeLineTerminator.setPollingQueue(lastQueue);
p.setTerminator(pipeLineTerminator);
p.setStages(stages);
return p;
}
@SuppressWarnings("unchecked")
<I extends StagePayload, O extends StagePayload>
void addStage(Stage<I, O> stage) {
Objects.requireNonNull(stage, "stage is required");
if (stages.isEmpty()) {
this.firstStage = (Stage<FIRST, ?>) stage;
}
stages.add(stage);
}
}

View file

@ -1,84 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.function.Consumer;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.service.task.Handler;
import ru.nbch.credit_tracker.service.task.Stage;
import ru.nbch.credit_tracker.service.task.setup.step.BuildStep;
import ru.nbch.credit_tracker.service.task.setup.step.HandlerStep;
import ru.nbch.credit_tracker.service.task.setup.step.PoisonStep;
/**
* Step Builder для Stage:
* Обязательная последовательность: handler(...) -> poison(...) -> buildStage()
*/
public class StageBuilderV2<FIRST extends StagePayload, I extends StagePayload, O extends StagePayload>
implements HandlerStep<FIRST, I, O>, PoisonStep<FIRST, I, O>, BuildStep<FIRST, O> {
private PipeLineBuilderV2<FIRST, O> parent;
private final BlockingQueue<I> inputQueue;
private final BlockingQueue<O> outputQueue;
private Handler<I, O> handler;
private O poison;
Consumer<O> consumer = null;
private StageBuilderV2(BlockingQueue<I> inputQueue, BlockingQueue<O> outputQueue) {
this.inputQueue = Objects.requireNonNull(inputQueue, "inputQueue is required");
this.outputQueue = Objects.requireNonNull(outputQueue, "outputQueue is required");
}
/** Стартовая фабрика возвращает первый шаг — HandlerStep */
public static <FIRST extends StagePayload, I extends StagePayload, O extends StagePayload>
HandlerStep<FIRST, I, O> create(BlockingQueue<I> inputQueue, BlockingQueue<O> outputQueue) {
return new StageBuilderV2<>(inputQueue, outputQueue);
}
void setParent(PipeLineBuilderV2<FIRST, O> parent) {
this.parent = Objects.requireNonNull(parent, "parent is required");
}
// ===================== ШАГИ =====================
/** Шаг 1: задать обработчик */
@Override
public PoisonStep<FIRST, I, O> handler(Handler<I, O> handler) {
this.handler = Objects.requireNonNull(handler, "handler is required");
return this;
}
/** Шаг 2: задать poison pill */
@Override
public BuildStep<FIRST, O> poison(O poison) {
this.poison = Objects.requireNonNull(poison, "poison is required");
return this;
}
public BuildStep<FIRST, O> handlerResult(Consumer<O> consumer) {
this.consumer = Objects.requireNonNull(consumer, "consumer is required");
return this;
}
/** Шаг 3: собрать Stage и вернуться к PipeLineBuilder */
@Override
public PipeLineBuilderV2<FIRST, O> andThen() {
if (parent == null) {
throw new IllegalStateException("parent PipeLineBuilder is not set");
}
if (handler == null) {
throw new IllegalStateException("handler must be set before buildStage()");
}
if (poison == null) {
throw new IllegalStateException("poison must be set before buildStage()");
}
Stage<I, O> stage = new Stage<>(handler, inputQueue, outputQueue, poison);
stage.setConsumer(consumer);
parent.addStage(stage);
return parent;
}
// ===================== Step-interfaces =====================
}

View file

@ -1,10 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup.step;
import java.util.function.Consumer;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.service.task.setup.PipeLineBuilderV2;
public interface BuildStep<FIRST extends StagePayload, O extends StagePayload> {
PipeLineBuilderV2<FIRST, O> andThen();
BuildStep<FIRST, O> handlerResult(Consumer<O> consumer);
}

View file

@ -1,8 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup.step;
import ru.nbch.credit_tracker.model.StagePayload;
import ru.nbch.credit_tracker.service.task.Handler;
public interface HandlerStep<FIRST extends StagePayload, I extends StagePayload, O extends StagePayload> {
PoisonStep<FIRST, I, O> handler(Handler<I, O> handler);
}

View file

@ -1,7 +0,0 @@
package ru.nbch.credit_tracker.service.task.setup.step;
import ru.nbch.credit_tracker.model.StagePayload;
public interface PoisonStep<FIRST extends StagePayload, I extends StagePayload, O extends StagePayload> {
BuildStep<FIRST, O> poison(O poison);
}

View file

@ -1,114 +0,0 @@
package ru.nbch.credit_tracker.service.xml.stax;
import java.io.OutputStream;
import java.util.List;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import lombok.extern.slf4j.Slf4j;
import ru.nbch.credit_tracker.entities.external_request.signals.Auth;
import ru.nbch.credit_tracker.entities.external_request.signals.Person;
import ru.nbch.credit_tracker.entities.external_request.signals.Signals;
@Slf4j
//todo сделать с этим что-то!!!
public class SignalsStaxXmlWriterV2 {
private final List<Person> personList;
private final Signals signals;
private final String charsetName;
public SignalsStaxXmlWriterV2(List<Person> personList,
Signals signals,
String charsetName) {
this.personList = personList;
this.signals = signals;
this.charsetName = charsetName;
}
public void writeWithStax(OutputStream ou) {
try {
XMLOutputFactory output = XMLOutputFactory.newInstance();
XMLEventFactory xmlEventFactory = XMLEventFactory.newInstance();
XMLEventWriter writer = output.createXMLEventWriter(ou, charsetName);
writer = new IndentingXMLEventWriter(writer);
WriterWrapper writerWrapper = new WriterWrapper(xmlEventFactory, writer);
// writerWrapper.addStartDocument(charsetName);
//
// writerWrapper.addStartElement("Signals");
// writerWrapper.addAttribute("online", signals.getOnline());
// writerWrapper.addAttribute("processWithErrors", signals.getProcessWithErrors());
// writerWrapper.addSimplelement("Id", signals.getId());
// writeAuth(writerWrapper, signals.getAuth());
//
// writerWrapper.addStartElement("Conditions");
writerWrapper.addStartElement("Persons");
writePersons(writerWrapper, personList);
writerWrapper.addEndElement("Persons");
// writerWrapper.addEndElement("Conditions");
// writerWrapper.addEndElement("Signals");
writerWrapper.addEndDocument();
writerWrapper.flush();
System.gc();
} catch (Throwable e) {
log.error("Error serializing object to XML string: {}", e.getMessage(), e);
throw new IllegalStateException("cannot serialize object to XML");
}
}
private void writePersons(WriterWrapper writerWrapper, List<Person> persons) throws Exception {
writerWrapper.writeCollection("p", persons, (obj, name) -> writePerson(writerWrapper, obj, name));
}
private void writeAuth(WriterWrapper writerWrapper, Auth auth) throws Exception {
writerWrapper.addStartElement("Auth");
writerWrapper.addSimplelement("User", auth.getUser());
writerWrapper.addEndElement("Auth");
}
public void writePerson(WriterWrapper writerWrapper, Person person, String name) throws Exception {
if (person == null) {
return;
}
writerWrapper.addStartElement(name);
writerWrapper.addAttribute("uid", String.valueOf(person.getUid()));
writerWrapper.addAttribute("oksm", person.getOksm());
writerWrapper.addAttribute("s", person.getS());
writerWrapper.addAttribute("n", person.getN());
writerWrapper.addAttribute("m", person.getM());
writerWrapper.addAttribute("d", person.getD());
writerWrapper.addAttribute("pn", person.getPn());
writerWrapper.addAttribute("pd", person.getPd());
writerWrapper.addAttribute("dt", person.getDt());
writerWrapper.addAttribute("odt", person.getOdt());
writerWrapper.addAttribute("ip", person.getIp());
writerWrapper.addAttribute("consentDate", person.getConsentDate());
writerWrapper.addAttribute("consentPurpose", person.getConsentPurpose());
writerWrapper.addAttribute("reportUser", person.getReportUser());
writerWrapper.addAttribute("liability", person.getLiability());
writerWrapper.addAttribute("consentPeriod", person.getConsentPeriod());
writerWrapper.addAttribute("agrDate", person.getAgrDate());
writerWrapper.addAttribute("reportUserRegNum", person.getReportUserRegNum());
writerWrapper.addAttribute("reportUserTaxID", person.getReportUserTaxID());
writerWrapper.addAttribute("consentHash", person.getConsentHash());
writerWrapper.addAttribute("cS", person.getCs());
writerWrapper.addAttribute("consentN", person.getConsentN());
writerWrapper.addAttribute("consentM", person.getConsentM());
writerWrapper.addAttribute("consentBd", person.getConsentBd());
writerWrapper.addAttribute("consentPn", person.getConsentPn());
writerWrapper.addAttribute("consentPd", person.getConsentPd());
writerWrapper.addAttribute("consentDivCode", person.getConsentDivCode());
writerWrapper.addAttribute("consentDt", person.getConsentDt());
writerWrapper.addAttribute("consentOdt", person.getConsentOdt());
writerWrapper.addAttribute("consentIssueAuthority", person.getConsentIssueAuthority());
writerWrapper.addAttribute("inqPurpose", person.getInqPurpose());
writerWrapper.addEndElement(name);
}
}

View file

@ -104,11 +104,7 @@ public class XmlProcessorStaxImpl implements StaxXmlProcessor {
} }
@Override @Override
public <Z> Z read(InputStream inputStream, public <Z> Z read(InputStream inputStream, Class<Z> clazz, Consumer<Z> objProcessor, Function<Z, Boolean> emptyBatchCheckFunction, Integer batchSize, String endBatchElementPath) {
Class<Z> clazz, Consumer<Z> objProcessor,
Function<Z, Boolean> emptyBatchCheckFunction,
Integer batchSize,
String endBatchElementPath) {
try { try {
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStream); XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStream);
Supplier<StaxReader<?>> staxReaderSupplier = staxReaders.get(clazz); Supplier<StaxReader<?>> staxReaderSupplier = staxReaders.get(clazz);

View file

@ -101,8 +101,4 @@ public class CTUtil {
log.warn("File {} hasn't been deleted", filePath); log.warn("File {} hasn't been deleted", filePath);
} }
} }
public static String normalizePhoneNumber(String phoneNumber) {
return "9" + phoneNumber;
}
} }

View file

@ -1,85 +0,0 @@
package ru.nbch.credit_tracker.utils;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import ru.nbch.credit_tracker.utils.error.ExceptionUtils;
public class LogUtil {
private final static Logger log = LoggerFactory.getLogger(LogUtil.class);
public static final String mdcKey = "ru.nbch.credit_tracker.log.mdc.key";
public static void addMdcLogKey(String uuid) {
addMdcLogKey(mdcKey, uuid);
}
public static void removeMdcLogKey() {
removeMdcLogKey(mdcKey);
}
public static void removeMdcLogKey(String key) {
try {
MDC.remove(key);
} catch (Throwable e) {
log.error(ExceptionUtils.getStackTrace(e));
}
}
public static void addMdcLogKey(String mdcKey, String uuid) {
if (uuid != null) {
try {
MDC.put(mdcKey, "[" + uuid + "] ");
} catch (Throwable e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
}
}
}
public static Map<String, String> extractMdcMap() {
try {
return MDC.getCopyOfContextMap();
} catch (Throwable e) {
log.error(ExceptionUtils.getStackTrace(e));
return null;
}
}
public static Runnable wrap(Map<String, String> contextMap, Runnable task) {
return () -> {
Map<String, String> previous = null;
try {
try {
previous = MDC.getCopyOfContextMap();
} catch (Throwable e) {
log.error(ExceptionUtils.getStackTrace(e));
}
// ставим захваченный контекст (может быть null)
try {
if (contextMap == null) {
MDC.clear();
} else {
MDC.setContextMap(contextMap);
}
} catch (Throwable e) {
log.error(ExceptionUtils.getStackTrace(e));
}
task.run();
} finally {
// восстанавливаем предыдущий (может быть null)
try {
if (previous == null) {
MDC.clear();
} else {
MDC.setContextMap(previous);
}
} catch (Throwable e) {
log.error(ExceptionUtils.getStackTrace(e));
}
}
};
}
}

View file

@ -1,15 +1,10 @@
package ru.nbch.credit_tracker.utils; package ru.nbch.credit_tracker.utils;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
@Slf4j @Slf4j
public class ZipUtils { public class ZipUtils {
@ -30,27 +25,4 @@ public class ZipUtils {
} }
return zipEntry; return zipEntry;
} }
public static boolean isZipFile(MultipartFile file) {
return file.getContentType() != null &&
(file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) ||
file.getOriginalFilename() != null && file.getOriginalFilename().toLowerCase().endsWith(".zip");
}
public static String writeTmpFile(String path, MultipartFile file) throws IOException {
CTUtil.createFolder(path);
String filePath = path + UUID.randomUUID() + "monitoring_request.xml";
try (OutputStream out = Files.newOutputStream(Path.of(filePath))) {
ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream());
zipInputStream.getNextEntry();
byte[] buffer = new byte[8 * 1024 * 1024];
int bytesRead;
while ((bytesRead = zipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
return filePath;
}
}
} }

View file

@ -1,7 +1,5 @@
package ru.nbch.credit_tracker.utils.error; package ru.nbch.credit_tracker.utils.error;
import java.io.PrintWriter;
import java.io.StringWriter;
import ru.nbch.credit_tracker.enums.EnumKeyMessage; import ru.nbch.credit_tracker.enums.EnumKeyMessage;
import ru.nbch.credit_tracker.enums.IEnumKeyCode; import ru.nbch.credit_tracker.enums.IEnumKeyCode;
import ru.nbch.credit_tracker.enums.XmlErrorFormat; import ru.nbch.credit_tracker.enums.XmlErrorFormat;
@ -10,13 +8,6 @@ import ru.nbch.credit_tracker.exceptions.SignalServerException;
public class ExceptionUtils { public class ExceptionUtils {
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
public static SignalClientException clientExcp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat, Object... args) { public static SignalClientException clientExcp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat, Object... args) {
return clientExcp(enumKeyCode, null, xmlErrorFormat, args); return clientExcp(enumKeyCode, null, xmlErrorFormat, args);
} }

View file

@ -63,18 +63,12 @@
</appender> </appender>
<!--SQL запросы и результаты их выполнения--> <!--SQL запросы и результаты их выполнения-->
<logger name="ru.nbch.credit_tracker.mapper" level="DEBUG" additivity="false"> <logger name="ru.nbch.credit_tracker.mapper" level="INFO" additivity="false">
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
<appender-ref ref="FILE-JSON"/> <appender-ref ref="FILE-JSON"/>
</logger> </logger>
<logger name="ru.nbch.credit_tracker.component" level="DEBUG" additivity="false"> <logger name="ru.nbch.credit_tracker.service" level="INFO" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="FILE-JSON"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="ru.nbch.credit_tracker.service" level="TRACE" additivity="false">
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
<appender-ref ref="FILE-JSON"/> <appender-ref ref="FILE-JSON"/>
<appender-ref ref="CONSOLE"/> <appender-ref ref="CONSOLE"/>

View file

@ -5,47 +5,12 @@
<mapper namespace="ru.nbch.credit_tracker.mapper.indic.FlagsMapper"> <mapper namespace="ru.nbch.credit_tracker.mapper.indic.FlagsMapper">
<resultMap id="RuPastDuarearResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$RuPastDuarearData"> <resultMap id="FlagsFidDataResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$FlagFidData">
<result property="fid" column="fid" />
<result property="accSerialNum" column="ACC_SERIAL_NUM" />
<result property="reportedDate" column="REPORTED_DT" />
<result property="amtPastDue" column="AMT_PAST_DUE" />
<result property="daysPastDue" column="DAYS_PAST_DUE" />
</resultMap>
<resultMap id="RuPaymtResultMap" type="ru.nbch.credit_tracker.service.indic.FlagsService$RuPaymtData">
<result property="fid" column="fid" />
<result property="daysPastDue" column="DAYS_PAST_DUE" />
<result property="reportedDate" column="REPORTED_DT" />
</resultMap>
<resultMap id="PaymentsFlagResult" type="ru.nbch.credit_tracker.entities.indic.PaymentsFlag">
<result property="fid" column="fid" /> <result property="fid" column="fid" />
<result property="flag" column="flag" /> <result property="flag" column="flag" />
</resultMap> </resultMap>
<select id="findRuPastDuarearData" resultMap="RuPastDuarearResultMap"> <select id="findFlagsDebt5000" resultMap="FlagsFidDataResultMap">
SELECT FID, ACC_SERIAL_NUM, REPORTED_DT, AMT_PAST_DUE, DAYS_PAST_DUE
FROM RU_PASTDUEARREAR
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
with UR
</select>
<select id="findRuPaymtData" resultMap="RuPaymtResultMap">
SELECT FID, DAYS_PAST_DUE, REPORTED_DT
FROM RU_PAYMT
WHERE FID IN
<foreach item="fid" collection="fids" open="(" separator="," close=")">
#{fid}
</foreach>
AND REPORTED_DT >= #{reportedDate}
with UR
</select>
<select id="findFlagsDebt5000" resultMap="PaymentsFlagResult">
SELECT SELECT
FID as fid, FID as fid,
(case when (sum(case when AMT_PAST_DUE > 5000 then 1 else 0 end) = 0) then 0 else 1 end) as flag (case when (sum(case when AMT_PAST_DUE > 5000 then 1 else 0 end) = 0) then 0 else 1 end) as flag
@ -62,7 +27,7 @@
WITH UR WITH UR
</select> </select>
<select id="findFlag90ByPastDueArrear" resultMap="PaymentsFlagResult"> <select id="findFlag90ByPastDueArrear" resultMap="FlagsFidDataResultMap">
SELECT SELECT
FID as fid, FID as fid,
(case when (sum(case when (REPORTED_DT >= #{reportedDate} and DAYS_PAST_DUE > 90) then 1 else 0 end) = 0) then 0 else 1 end) as flag (case when (sum(case when (REPORTED_DT >= #{reportedDate} and DAYS_PAST_DUE > 90) then 1 else 0 end) = 0) then 0 else 1 end) as flag
@ -75,7 +40,7 @@
WITH UR WITH UR
</select> </select>
<select id="findFlag90ByRuPaymt" resultMap="PaymentsFlagResult"> <select id="findFlag90ByRuPaymt" resultMap="FlagsFidDataResultMap">
SELECT SELECT
FID as fid, FID as fid,
(case when (sum(case when (REPORTED_DT >= #{reportedDate} and DAYS_PAST_DUE > 90) then 1 else 0 end) = 0) then 0 else 1 end) as flag (case when (sum(case when (REPORTED_DT >= #{reportedDate} and DAYS_PAST_DUE > 90) then 1 else 0 end) = 0) then 0 else 1 end) as flag

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="ru.nbch.credit_tracker.mapper.indic.PhoneMapper">
<resultMap id="fidsDataResultMap" type="ru.nbch.credit_tracker.service.indic.PhoneService$FidsData">
<result property="phone" column="number" />
<result property="fid" column="fid" />
<result property="fileSinceDate" column="fileSinceDate" />
</resultMap>
<select id="findFids" resultMap="fidsDataResultMap">
SELECT pn.NUMBER AS number, pn.FID AS fid, pn.FILE_SINCE_DT AS fileSinceDate
FROM PHONE_NORM pn
INNER JOIN NAME n ON n.FID = pn.FID
WHERE number in
<foreach item="phone" collection="phones" open="(" separator="," close=")">
#{phone}
</foreach>
ORDER BY fileSinceDate DESC, fid DESC
WITH UR
</select>
</mapper>

View file

@ -5,7 +5,7 @@
<mapper namespace="ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper"> <mapper namespace="ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper">
<resultMap id="PhoneFidMapResultMap" type="ru.nbch.credit_tracker.entities.app.PhoneFidMap"> <resultMap id="PhoneFidMapResultMap" type="ru.nbch.credit_tracker.entities.dto.PhoneFidMap">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="packageId" column="package_id"/> <result property="packageId" column="package_id"/>
<result property="subjectId" column="subject_id"/> <result property="subjectId" column="subject_id"/>
@ -25,36 +25,6 @@
</foreach> </foreach>
</insert> </insert>
<insert id="register" parameterType="java.util.List">
INSERT INTO signals_phone_fid_map (subject_id, fid, phone, package_id, flag_debt5000, flag_90_12, calculated_at)
values
<foreach collection="phoneFidMap" item="phoneFid" separator=",">
(#{phoneFid.subjectId}, #{phoneFid.fid}, #{phoneFid.phone}, #{phoneFid.packageId}, #{phoneFid.flagDebt5000}, #{phoneFid.flag90days}, #{phoneFid.calculatedAt})
</foreach>
</insert>
<insert id="registerJson" parameterType="string">
INSERT INTO signals_phone_fid_map
(subject_id, fid, phone, package_id, flag_debt5000, flag_90_12, calculated_at)
SELECT subject_id, fid, phone, package_id, flag_debt5000, flag_90_12, calculated_at
FROM json_to_recordset(#{json}::json) AS t(
subject_id varchar(255),
fid int,
phone text,
package_id bigint,
flag_debt5000 smallint,
flag_90_12 smallint,
calculated_at timestamp)
</insert>
<insert id="registerWithId" parameterType="java.util.List" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO signals_phone_fid_map (subject_id, phone, package_id)
values
<foreach collection="phoneFidMap" item="phoneFid" separator=",">
(#{phoneFid.subjectId}, #{phoneFid.phone}, #{phoneFid.packageId})
</foreach>
</insert>
<update id="updatePhoneFidMaps" parameterType="java.util.List"> <update id="updatePhoneFidMaps" parameterType="java.util.List">
<foreach collection="subjects" item="subject" separator=";"> <foreach collection="subjects" item="subject" separator=";">
@ -72,35 +42,12 @@
</foreach> </foreach>
</update> </update>
<update id="updateOne" parameterType="ru.nbch.credit_tracker.entities.app.PhoneFidMap">
UPDATE signals_phone_fid_map
SET
package_id = #{subject.packageId},
subject_id = #{subject.subjectId},
phone = #{subject.phone},
fid = #{subject.fid2},
flag_debt5000 = #{subject.flagDebt5000},
flag_90_12 = #{subject.flag90days},
calculated_at = #{subject.calculatedAt},
error_text = #{subject.errorText}
WHERE id = #{subject.id}
</update>
<update id="updateFid" parameterType="java.util.List">
<foreach collection="subjects" item="subject" separator=";">
UPDATE signals_phone_fid_map
SET
fid = #{subject.fid}
WHERE rec_uuid = #{subject.id}
</foreach>
</update>
<update id="updatePhoneFidMapsErrors" parameterType="java.util.List"> <update id="updatePhoneFidMapsErrors" parameterType="java.util.List">
<foreach collection="subjects" item="subject" separator=";"> <foreach collection="subjects" item="subject" separator=";">
UPDATE signals_phone_fid_map UPDATE signals_phone_fid_map
SET SET
error_text = #{subject.errorText} error_text = #{subject.errorText}
WHERE id = #{subject.recUuid} WHERE id = #{subject.id}
</foreach> </foreach>
</update> </update>
@ -120,7 +67,7 @@
LIMIT #{limit} OFFSET #{offset} LIMIT #{limit} OFFSET #{offset}
</select> </select>
<select id="findPhoneFidMapById" resultMap="PhoneFidMapResultMap" resultType="ru.nbch.credit_tracker.entities.app.PhoneFidMap"> <select id="findPhoneFidMapById" resultMap="PhoneFidMapResultMap" resultType="ru.nbch.credit_tracker.entities.dto.PhoneFidMap">
SELECT id, SELECT id,
package_id, package_id,
subject_id, subject_id,
@ -141,5 +88,11 @@
where package_id = #{packageId} AND fid is not null where package_id = #{packageId} AND fid is not null
</select> </select>
<select id="hasOldRow" resultType="java.lang.Boolean">
select EXISTS(SELECT 1
FROM signals_phone_fid_map
WHERE package_id = #{packageId}
AND calculated_at &lt; now() - make_interval(days => #{daysCount})
)
</select>
</mapper> </mapper>

View file

@ -5,7 +5,7 @@
<mapper namespace="ru.nbch.credit_tracker.mapper.signal.UserPackageMapper"> <mapper namespace="ru.nbch.credit_tracker.mapper.signal.UserPackageMapper">
<resultMap id="packageDataResultMap" type="ru.nbch.credit_tracker.entities.app.UserPackage"> <resultMap id="packageDataResultMap" type="ru.nbch.credit_tracker.entities.dto.UserPackage">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="userId" column="membercode"/> <result property="userId" column="membercode"/>
<result property="packageName" column="package_name"/> <result property="packageName" column="package_name"/>
@ -53,7 +53,7 @@
</update> </update>
<select id="insertPackage" resultType="java.lang.Long" <select id="insertPackage" resultType="java.lang.Long"
parameterType="ru.nbch.credit_tracker.entities.app.UserPackage"> parameterType="ru.nbch.credit_tracker.entities.dto.UserPackage">
INSERT INTO signals_user_packages (membercode, package_name, created_at, status) INSERT INTO signals_user_packages (membercode, package_name, created_at, status)
values (#{userPackage.userId}, #{userPackage.packageName}, #{userPackage.createdAt}, #{userPackage.status}) RETURNING id values (#{userPackage.userId}, #{userPackage.packageName}, #{userPackage.createdAt}, #{userPackage.status}) RETURNING id
</select> </select>
@ -77,7 +77,7 @@
</update> </update>
<select id="findPackage" resultMap="packageDataResultMap" <select id="findPackage" resultMap="packageDataResultMap"
resultType="ru.nbch.credit_tracker.entities.app.UserPackage"> resultType="ru.nbch.credit_tracker.entities.dto.UserPackage">
SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at
FROM signals_user_packages FROM signals_user_packages
WHERE id = #{id} WHERE id = #{id}
@ -85,7 +85,7 @@
</select> </select>
<select id="findUserPackages" resultMap="packageDataResultMap" <select id="findUserPackages" resultMap="packageDataResultMap"
resultType="ru.nbch.credit_tracker.entities.app.UserPackage"> resultType="ru.nbch.credit_tracker.entities.dto.UserPackage">
SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at
FROM signals_user_packages FROM signals_user_packages
WHERE membercode IS NOT NULL WHERE membercode IS NOT NULL
@ -97,7 +97,7 @@
</select> </select>
<select id="findAllPackagesByUserId" resultMap="packageDataResultMap" <select id="findAllPackagesByUserId" resultMap="packageDataResultMap"
resultType="ru.nbch.credit_tracker.entities.app.UserPackage"> resultType="ru.nbch.credit_tracker.entities.dto.UserPackage">
SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at
FROM signals_user_packages FROM signals_user_packages
WHERE membercode IS NOT NULL WHERE membercode IS NOT NULL
@ -105,7 +105,7 @@
</select> </select>
<select id="findAllPackagesByStatus" resultMap="packageDataResultMap" <select id="findAllPackagesByStatus" resultMap="packageDataResultMap"
resultType="ru.nbch.credit_tracker.entities.app.UserPackage"> resultType="ru.nbch.credit_tracker.entities.dto.UserPackage">
SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at SELECT id, membercode, package_name, created_at, status, deactivated_at, last_downloaded_report, monitoring_at
FROM signals_user_packages FROM signals_user_packages
WHERE status = #{status} WHERE status = #{status}

View file

@ -0,0 +1,3 @@
CREATE INDEX IF NOT EXISTS
idx_signals_phone_fid_map_calculated_at
ON signals_phone_fid_map (calculated_at);

View file

@ -1,24 +0,0 @@
CREATE TABLE signals_user_packages (
DB_VERSION varchar(12)
);
alter table signals_user_packages
alter column LAST_SEARCHED_AT type timestamp;
alter table signals_user_packages
alter column REJECT_STATUS type varchar(255);
alter table signals_phone_fid_map
alter column SEARCHED_AT type timestamp;
CREATE TABLE signals_flags (
ID SERIAL PRIMARY KEY,
MAP_ID bigint,
SUBJECT_ID varchar(255),
PHONE varchar(255),
FID int,
FLAG_DEBT5000 smallint,
FLAG_90_12 smallint,
CALCULATED_AT timestamp
);

View file

@ -1,278 +0,0 @@
package ru.nbch.credit_tracker.service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import ru.nbch.credit_tracker.service.indic.FlagsService;
public class FlagsService2Test {
private static final LocalDate INITIAL_DATE = LocalDate.parse("2025-09-25");
private static final String RU_PASTDUEAREAR_CREATE = """
create table RU_PASTDUEARREAR
(
FID INTEGER not null,
SERIAL_NUM BIGINT not null primary key,
ACC_SERIAL_NUM BIGINT not null,
REPORTED_DT DATE not null,
AMT_PAST_DUE DECIMAL(17, 2),
DAYS_PAST_DUE BIGINT
);
""";
private static final String RU_PASTDUEAREAR_DROP = """
DROP TABLE IF EXISTS RU_PASTDUEARREAR;
""";
private static final String RU_PAYMT_DROP = """
DROP TABLE IF EXISTS RU_PAYMT;
""";
private static final String RU_PAYMT_CREATE = """
create table RU_PAYMT
(
FID INTEGER not null,
SERIAL_NUM BIGINT not null primary key,
DAYS_PAST_DUE INTEGER,
REPORTED_DT DATE not null
);
""";
private static final String FILL_RU_PASTDUEARREAR = String.format("""
INSERT INTO RU_PASTDUEARREAR VALUES
(1, 1, 1, '%s', 36000.00, 159), -- просрочка 5000 и 90_days по этой записи
(2, 2, 2, '%s', 3200.00, null), -- не макс reported_date для того же acc_serial_num
(2, 3, 2, '%s', 6700.00, null), -- не макс reported_date для того же acc_serial_num
(2, 4, 2, '%s', 5700.00, null), -- просрочка 5000 по этой записи
(3, 5, 3, '%s', 300000000000.00, null),
(2, 6, 2, '%s', 0.00, null), -- не макс reported_date для того же acc_serial_num
(4, 7, 4, '%s', 4000.00, null), -- нет просрочек
(5, 8, 5, '%s', 2000.31, 182), -- не макс reported_date для того же acc_serial_num
(5, 9, 5, '%s', 2000.31, 212), -- не макс reported_date для того же acc_serial_num
(5, 10, 5, '%s', 2000.31, null), -- не макс reported_date для того же acc_serial_num
(5, 11, 5, '%s', 2000.31, 273), -- не макс reported_date для того же acc_serial_num
(5, 12, 5, '%s', 2000.31, null), -- не макс reported_date для того же acc_serial_num
(5, 13, 5, '%s', 7000.31, 307), -- не макс reported_date для того же acc_serial_num
(5, 14, 5, '%s', 2000.31, 338), -- не макс reported_date для того же acc_serial_num, просрочка 90_days будет взята у этой записи
(5, 15, 5, '%s', 7000.31, 368), -- Просрочка 5000 будет взята у этой записи. Уникальный acc_serial_num с макс reported_dt
(5, 16, 5, '%s', 2000.31, 399), -- не макс reported_date для того же acc_serial_num
(5, 17, 5, '%s', 5000.31, 5), -- не макс reported_date для того же acc_serial_num
(6, 18, 6, '%s', 3000, null), -- Нет просрочки 5000, нет просрочки 90_days (90_days будет в RU_PAYMT)
(7, 19, 7, '%s', 5500, 90), -- не макс reported_date для того же acc_serial_num. 90_days будет взята отсюда
(7, 20, 7, '%s', 3000, null) -- Нет просрочки 5000. Уникальный acc_serial_num с макс reported_dt
""",
calcLocalDateFromInitialDate("2025-05-31"),
calcLocalDateFromInitialDate("2021-12-29"),
calcLocalDateFromInitialDate("2022-01-28"),
calcLocalDateFromInitialDate("2025-04-06"),
calcLocalDateFromInitialDate("2025-01-14"),
calcLocalDateFromInitialDate("2021-11-29"),
calcLocalDateFromInitialDate("2025-12-06"),
calcLocalDateFromInitialDate("2021-04-01"),
calcLocalDateFromInitialDate("2021-05-01"),
calcLocalDateFromInitialDate("2021-05-31"),
calcLocalDateFromInitialDate("2021-07-01"),
calcLocalDateFromInitialDate("2021-07-31"),
calcLocalDateFromInitialDate("2021-08-04"),
calcLocalDateFromInitialDate("2025-09-08"),
calcLocalDateFromInitialDate("2025-09-09"),
calcLocalDateFromInitialDate("2021-11-04"),
calcLocalDateFromInitialDate("2022-01-04"),
calcLocalDateFromInitialDate("2025-09-09"),
calcLocalDateFromInitialDate("2025-09-09"),
calcLocalDateFromInitialDate("2025-09-10"));
private static final String FILL_RU_PAYMT = String.format("""
INSERT INTO RU_PAYMT VALUES
(1, 1, null, '%s'),
(2, 2, 30, '%s'),
(3, 3, null, '%s'),
(3, 4, 100, '%s'),
(4, 5, 10, '%s'),
(5, 6, 10, '%s'),
(6, 7, 90, '%s'),
(6, 8, 80, '%s'),
(7, 9, null, '%s'),
(8, 10, null, '%s'),
(8, 11, 83, '%s'),
(8, 12, 91, '%s'),
(9, 13, 100, '%s')
""",
calcLocalDateFromInitialDate("2024-11-09"),
calcLocalDateFromInitialDate("2024-11-09"),
calcLocalDateFromInitialDate("2024-11-10"),
calcLocalDateFromInitialDate("2024-11-15"),
calcLocalDateFromInitialDate("2024-11-15"),
calcLocalDateFromInitialDate("2023-11-15"),
calcLocalDateFromInitialDate("2024-11-09"),
calcLocalDateFromInitialDate("2024-10-09"),
calcLocalDateFromInitialDate("2024-10-09"),
calcLocalDateFromInitialDate("2024-10-09"),
calcLocalDateFromInitialDate("2024-10-09"),
calcLocalDateFromInitialDate("2024-10-09"),
calcLocalDateFromInitialDate("2024-10-09"));
private DataSource dataSource;
private NamedParameterJdbcTemplate jdbcTemplate;
private FlagsService flagsService;
@BeforeEach
void setUp() {
dataSource = DataSourceBuilder.create()
.url("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=DB2;DATABASE_TO_LOWER=TRUE")
.driverClassName("org.h2.Driver")
.username("sa")
.password("")
.build();
jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
jdbcTemplate.getJdbcTemplate().execute(RU_PASTDUEAREAR_DROP);
jdbcTemplate.getJdbcTemplate().execute(RU_PAYMT_DROP);
jdbcTemplate.getJdbcTemplate().execute(RU_PASTDUEAREAR_CREATE);
jdbcTemplate.getJdbcTemplate().execute(RU_PAYMT_CREATE);
flagsService = new FlagsService(null, jdbcTemplate);
}
@Test
public void testfindFlags() {
jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PASTDUEARREAR);
jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PAYMT);
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
}
@Test
public void testfindFlagsWithoutRuPaymt() {
jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PASTDUEARREAR);
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(0, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
Assertions.assertEquals(0, resultMap.get(6).getFlag90days());
Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
Assertions.assertEquals(0, resultMap.get(8).getFlag90days());
Assertions.assertEquals(0, resultMap.get(9).getFlag90days());
}
@Test
public void testfindFlagsWithoutRuPastduearreae() {
jdbcTemplate.getJdbcTemplate().execute(FILL_RU_PAYMT);
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(0, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(0, resultMap.get(5).getFlag90days());
Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
Assertions.assertEquals(0, resultMap.get(7).getFlag90days());
Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
}
private static LocalDate calcLocalDateFromInitialDate(String date) {
LocalDateTime now = LocalDateTime.now();
long daysPassed = ChronoUnit.DAYS.between(INITIAL_DATE, now);
return LocalDate.parse(date).plusDays(daysPassed);
}
}

View file

@ -1,218 +0,0 @@
package ru.nbch.credit_tracker.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
import ru.nbch.credit_tracker.service.indic.FlagsService;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class FlagsServiceTest {
private static final LocalDate INITIAL_DATE = LocalDate.parse("2025-09-25");
@Mock
FlagsMapper flagsMapper;
@Test
public void testfindFlags() {
FlagsService flagsService = new FlagsService(flagsMapper, null);
when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(ruPastDuarearDataList);
when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(ruPaymtDataList);
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
}
@Test
public void testfindFlagsWithoutRuPaymt() {
FlagsService flagsService = new FlagsService(flagsMapper, null);
when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(ruPastDuarearDataList);
when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(new ArrayList<>());
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(1, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(1, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(0, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(1, resultMap.get(5).getFlag90days());
Assertions.assertEquals(0, resultMap.get(6).getFlag90days());
Assertions.assertEquals(1, resultMap.get(7).getFlag90days());
Assertions.assertEquals(0, resultMap.get(8).getFlag90days());
Assertions.assertEquals(0, resultMap.get(9).getFlag90days());
}
@Test
public void testfindFlagsWithoutRuPastduearreae() {
FlagsService flagsService = new FlagsService(flagsMapper, null);
when(flagsMapper.findRuPastDuarearData(Mockito.anyList())).thenReturn(new ArrayList<>());
when(flagsMapper.findRuPaymtData(Mockito.anyList(), Mockito.any())).thenReturn(ruPaymtDataList);
List<Integer> fids = new ArrayList<>(List.of(
1, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
2, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Нет просрочки 90_days в RU_PAYMT
3, // есть просрочка 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
4, // нет никаких просрочек
7, // нет просрочки 5000, есть просрочка 90_days в RU_PASTDUEARREAR
5, // есть просрочка 5000, есть просрочка 90_days в RU_PASTDUEARREAR
6, // нет просрочки 5000, нет просрочки 90_days в RU_PASTDUEARREAR. Есть просрочка 90_days в RU_PAYMT
8, // нету в RU_PASTDUEARREAR, есть в RU_PAYMT
9) // нету в RU_PASTDUEARREAR, нету в RU_PAYMT
);
Map<Integer, FlagsService.FlagsData> resultMap = flagsService.findFlags(fids);
Assertions.assertEquals(9, resultMap.size());
Assertions.assertEquals(0, resultMap.get(1).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(2).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(3).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(4).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(5).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(6).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(7).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(8).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(9).getFlagDebt5000());
Assertions.assertEquals(0, resultMap.get(1).getFlag90days());
Assertions.assertEquals(0, resultMap.get(2).getFlag90days());
Assertions.assertEquals(1, resultMap.get(3).getFlag90days());
Assertions.assertEquals(0, resultMap.get(4).getFlag90days());
Assertions.assertEquals(0, resultMap.get(5).getFlag90days());
Assertions.assertEquals(1, resultMap.get(6).getFlag90days());
Assertions.assertEquals(0, resultMap.get(7).getFlag90days());
Assertions.assertEquals(1, resultMap.get(8).getFlag90days());
Assertions.assertEquals(1, resultMap.get(9).getFlag90days());
}
private final List<FlagsService.RuPastDuarearData> ruPastDuarearDataList = new ArrayList<>(
List.of(
new FlagsService.RuPastDuarearData(1, BigInteger.valueOf(1), calcLocalDateFromInitialDate("2025-05-31"), BigDecimal.valueOf(36000.00), BigInteger.valueOf(159)), // просрочка 5000 и 90_days по этой записи
new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2021-12-29"), BigDecimal.valueOf(3200.00), null), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2022-01-28"), BigDecimal.valueOf(6700.00), null), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2025-04-06"), BigDecimal.valueOf(5700.00), null), // просрочка 5000 по этой записи
new FlagsService.RuPastDuarearData(3, BigInteger.valueOf(3), calcLocalDateFromInitialDate("2025-01-14"), BigDecimal.valueOf(300000000000.00), null),
new FlagsService.RuPastDuarearData(2, BigInteger.valueOf(2), calcLocalDateFromInitialDate("2021-11-29"), BigDecimal.valueOf(0.00), null), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(4, BigInteger.valueOf(4), calcLocalDateFromInitialDate("2025-12-06"), BigDecimal.valueOf(4000.00), null), // нет просрочек
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-04-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(182)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-05-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(212)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-05-31"), BigDecimal.valueOf(2000.31), null), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-07-01"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(273)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-07-31"), BigDecimal.valueOf(2000.31), null), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-08-04"), BigDecimal.valueOf(7000.31), BigInteger.valueOf(307)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2025-09-08"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(338)), // не макс reported_date для того же acc_serial_num, просрочка 90_days будет взята у этой записи
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(51), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(7000.31), BigInteger.valueOf(368)), // Просрочка 5000 будет взята у этой записи. Уникальный acc_serial_num с макс reported_dt
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2021-11-04"), BigDecimal.valueOf(2000.31), BigInteger.valueOf(399)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(5, BigInteger.valueOf(5), calcLocalDateFromInitialDate("2022-01-04"), BigDecimal.valueOf(5000.31), BigInteger.valueOf(5)), // не макс reported_date для того же acc_serial_num
new FlagsService.RuPastDuarearData(6, BigInteger.valueOf(6), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(3000), null), // Нет просрочки 5000, нет просрочки 90_days (90_days будет в RU_PAYMT)
new FlagsService.RuPastDuarearData(7, BigInteger.valueOf(7), calcLocalDateFromInitialDate("2025-09-09"), BigDecimal.valueOf(5500), BigInteger.valueOf(90)), // не макс reported_date для того же acc_serial_num. 90_days будет взята отсюда
new FlagsService.RuPastDuarearData(7, BigInteger.valueOf(7), calcLocalDateFromInitialDate("2025-09-10"), BigDecimal.valueOf(3000), null) // Нет просрочки 5000. Уникальный acc_serial_num с макс reported_dt
)
);
private final List<FlagsService.RuPaymtData> ruPaymtDataList = new ArrayList<>(
List.of(
new FlagsService.RuPaymtData(1, null, calcLocalDateFromInitialDate("2024-11-09")),
new FlagsService.RuPaymtData(2, BigInteger.valueOf(30), calcLocalDateFromInitialDate("2024-11-09")),
new FlagsService.RuPaymtData(3, null, calcLocalDateFromInitialDate("2024-11-10")),
new FlagsService.RuPaymtData(3, BigInteger.valueOf(100), calcLocalDateFromInitialDate("2024-11-15")),
new FlagsService.RuPaymtData(4, BigInteger.valueOf(10), calcLocalDateFromInitialDate("2024-11-15")),
new FlagsService.RuPaymtData(5, BigInteger.valueOf(10), calcLocalDateFromInitialDate("2023-11-15")),
new FlagsService.RuPaymtData(6, BigInteger.valueOf(90), calcLocalDateFromInitialDate("2024-11-09")),
new FlagsService.RuPaymtData(6, BigInteger.valueOf(80), calcLocalDateFromInitialDate("2024-10-09")),
new FlagsService.RuPaymtData(7, null, calcLocalDateFromInitialDate("2024-10-09")),
new FlagsService.RuPaymtData(8, null, calcLocalDateFromInitialDate("2024-10-09")),
new FlagsService.RuPaymtData(8, BigInteger.valueOf(83), calcLocalDateFromInitialDate("2024-10-09")),
new FlagsService.RuPaymtData(8, BigInteger.valueOf(91), calcLocalDateFromInitialDate("2024-10-09")),
new FlagsService.RuPaymtData(9, BigInteger.valueOf(100), calcLocalDateFromInitialDate("2024-10-09"))
)
);
private LocalDate calcLocalDateFromInitialDate(String date) {
LocalDateTime now = LocalDateTime.now();
long daysPassed = ChronoUnit.DAYS.between(INITIAL_DATE, now);
return LocalDate.parse(date).plusDays(daysPassed);
}
}

View file

@ -1,179 +0,0 @@
package ru.nbch.credit_tracker.service;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import ru.nbch.credit_tracker.service.indic.PhoneService;
@ExtendWith(MockitoExtension.class)
public class PhoneService2Test {
@Mock
PhoneRepository phoneRepository;
@Test
public void defineFidByPhoneTest() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFids(any())).thenReturn(fidsDataWithEqualSinceDt);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(4, res.size());
Assertions.assertEquals("1111", res.get(124).getPhone());
Assertions.assertEquals("1112", res.get(125).getPhone());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
@Test
public void defineFidByPhoneTest2() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFids(any())).thenReturn(fidsDataWithEqualSinceDt2);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(3, res.size());
Assertions.assertEquals("1112", res.get(125).getPhone());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
@Test
public void defineFidByPhoneTest3() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFids(any())).thenReturn(fidsDataWithEqualSinceDt3);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(2, res.size());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
private final List<PhoneRepository.FidsData> fidsDataWithEqualSinceDt = new ArrayList<>(
List.of(
new PhoneRepository.FidsData(123, "91111", LocalDate.of(2025, 9, 10)),
new PhoneRepository.FidsData(124, "91111", LocalDate.of(2025, 9, 10)),
new PhoneRepository.FidsData(125, "91112", LocalDate.of(2024, 10, 10)),
new PhoneRepository.FidsData(126, "91114", LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsData(127, "91115", LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsData(128, "91115", LocalDate.of(2025, 9, 10))
)
);
private final List<PhoneRepository.FidsData> fidsDataWithEqualSinceDt2 = new ArrayList<>(
List.of(
new PhoneRepository.FidsData(125, "91112", LocalDate.of(2024, 10, 10)),
new PhoneRepository.FidsData(126, "91114", LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsData(127, "91115", LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsData(128, "91115", LocalDate.of(2025, 9, 10))
)
);
private final List<PhoneRepository.FidsData> fidsDataWithEqualSinceDt3 = new ArrayList<>(
List.of(
new PhoneRepository.FidsData(126, "91114", LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsData(127, "91115", LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsData(128, "91115", LocalDate.of(2025, 9, 10))
)
);
Comparator<PhoneRepository.FidsData> byDbOrder =
Comparator.comparing(PhoneRepository.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
.thenComparing(PhoneRepository.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
.thenComparing(PhoneRepository.FidsData::getFid, Comparator.reverseOrder());
@Test
void defineFidsAndNameByPhoneTest() {
PhoneService phoneService = new PhoneService(phoneRepository);
// fidsDataWithNameEqualSinceDt.sort(byDbOrderWithName);
when(phoneRepository.findFidsWithName(any())).thenReturn(fidsDataWithNameEqualSinceDt);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidsAndNameByPhone(phoneFidMap);
Assertions.assertEquals(4, res.size());
Assertions.assertEquals("1111", res.get(124).getPhone());
Assertions.assertEquals("1112", res.get(125).getPhone());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
Assertions.assertEquals("found124", res.get(124).getFirstName());
Assertions.assertNull(res.get(125).getFirstName());
Assertions.assertEquals("found126", res.get(126).getFirstName());
Assertions.assertNull(res.get(128));
}
private final List<PhoneRepository.FidsDataWithName> fidsDataWithNameEqualSinceDt = new ArrayList<>(
List.of(
new PhoneRepository.FidsDataWithName(123, "91111", LocalDate.of(2025, 9, 10), "skip", null, null, null, null),
new PhoneRepository.FidsDataWithName(123, "91111", LocalDate.of(2025, 9, 10), "skip", null, null, null, LocalDate.of(2025, 10, 9)),
new PhoneRepository.FidsDataWithName(124, "91111", LocalDate.of(2025, 9, 9), "found124", null, null, null, LocalDate.of(2025, 9, 9)),
new PhoneRepository.FidsDataWithName(124, "91111", LocalDate.of(2025, 9, 10), "skip124", null, null, null, LocalDate.of(2025, 9, 8)),
new PhoneRepository.FidsDataWithName(124, "91111", LocalDate.of(2025, 9, 9), "skip124", null, null, null, LocalDate.of(2025, 9, 8)),
new PhoneRepository.FidsDataWithName(124, "91111", LocalDate.of(2025, 9, 10), "found124", null, null, null, LocalDate.of(2025, 9, 9)),
new PhoneRepository.FidsDataWithName(125, "91112", LocalDate.of(2024, 10, 10), null, null, null, null, null),
new PhoneRepository.FidsDataWithName(126, "91114", LocalDate.of(2024, 10, 11), "skip126", null, null, null, null),
new PhoneRepository.FidsDataWithName(126, "91114", LocalDate.of(2024, 10, 11), "found126", null, null, null, LocalDate.of(2025, 9, 9)),
new PhoneRepository.FidsDataWithName(126, "91114", LocalDate.of(2024, 10, 5), "found126", null, null, null, LocalDate.of(2025, 9, 9)),
new PhoneRepository.FidsDataWithName(126, "91114", LocalDate.of(2024, 10, 5), "skip126", null, null, null, null),
new PhoneRepository.FidsDataWithName(127, "91115", LocalDate.of(2025, 9, 11), null, null, null, null, null),
new PhoneRepository.FidsDataWithName(128, "91115", LocalDate.of(2025, 9, 10), "found128", null, null, null, LocalDate.of(2025, 9, 9))
)
);
//todo понять зачем в тесте использовался этот компоратор
// Comparator<PhoneRepository.FidsDataWithName> byDbOrderWithName =
// Comparator.comparing(PhoneRepository.FidsDataWithName::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
// .thenComparing(PhoneRepository.FidsDataWithName::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
// .thenComparing(PhoneRepository.FidsDataWithName::getFid, Comparator.reverseOrder());
}

View file

@ -1,122 +0,0 @@
package ru.nbch.credit_tracker.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import ru.nbch.credit_tracker.service.indic.PhoneService;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class PhoneServiceTest {
@Mock
PhoneRepository phoneRepository;
@Test
public void defineFidByPhoneTest() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFidsLong(any())).thenReturn(fidsDataWithEqualSinceDt);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(4, res.size());
Assertions.assertEquals("1111", res.get(124).getPhone());
Assertions.assertEquals("1112", res.get(125).getPhone());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
@Test
public void defineFidByPhoneTest2() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFidsLong(any())).thenReturn(fidsDataWithEqualSinceDt2);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(3, res.size());
Assertions.assertEquals("1112", res.get(125).getPhone());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
@Test
public void defineFidByPhoneTest3() {
PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder);
when(phoneRepository.findFidsLong(any())).thenReturn(fidsDataWithEqualSinceDt3);
List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1112").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1113").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1114").build());
phoneFidMap.add(PhoneFidMap.builder().phone("1115").build());
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
Assertions.assertEquals(2, res.size());
Assertions.assertEquals("1114", res.get(126).getPhone());
Assertions.assertEquals("1115", res.get(127).getPhone());
}
private final List<PhoneRepository.FidsDataLong> fidsDataWithEqualSinceDt = new ArrayList<>(
List.of(
new PhoneRepository.FidsDataLong(123, 91111L, LocalDate.of(2025, 9, 10)),
new PhoneRepository.FidsDataLong(124, 91111L, LocalDate.of(2025, 9, 10)),
new PhoneRepository.FidsDataLong(125, 91112L, LocalDate.of(2024, 10, 10)),
new PhoneRepository.FidsDataLong(126, 91114L, LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsDataLong(127, 91115L, LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsDataLong(128, 91115L, LocalDate.of(2025, 9, 10))
)
);
private final List<PhoneRepository.FidsDataLong> fidsDataWithEqualSinceDt2 = new ArrayList<>(
List.of(
new PhoneRepository.FidsDataLong(125, 91112L, LocalDate.of(2024, 10, 10)),
new PhoneRepository.FidsDataLong(126, 91114L, LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsDataLong(127, 91115L, LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsDataLong(128, 91115L, LocalDate.of(2025, 9, 10))
)
);
private final List<PhoneRepository.FidsDataLong> fidsDataWithEqualSinceDt3 = new ArrayList<>(
List.of(
new PhoneRepository.FidsDataLong(126, 91114L, LocalDate.of(2024, 10, 11)),
new PhoneRepository.FidsDataLong(127, 91115L, LocalDate.of(2025, 9, 11)),
new PhoneRepository.FidsDataLong(128, 91115L, LocalDate.of(2025, 9, 10))
)
);
Comparator<PhoneRepository.FidsDataLong> byDbOrder =
Comparator.comparing(PhoneRepository.FidsDataLong::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
.thenComparing(PhoneRepository.FidsDataLong::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
.thenComparing(PhoneRepository.FidsDataLong::getFid, Comparator.reverseOrder());
}

View file

@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import ru.nbch.credit_tracker.entities.app.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors; import ru.nbch.credit_tracker.entities.external_response.monitoring_report_error.MonitoringReportErrors;
import ru.nbch.credit_tracker.entities.external_response.online_download.Person; import ru.nbch.credit_tracker.entities.external_response.online_download.Person;
import ru.nbch.credit_tracker.entities.external_response.online_download.Persons; import ru.nbch.credit_tracker.entities.external_response.online_download.Persons;