logging rest request; logging query time for every mapper; optimization query and refactoring some methods
This commit is contained in:
parent
fdbec299d6
commit
bddc5bb1fa
14 changed files with 482 additions and 192 deletions
139
pom.xml
139
pom.xml
|
|
@ -27,6 +27,10 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
|
|
@ -122,8 +126,83 @@
|
|||
<version>3.0.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>docker-push-dev</id>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>0.45.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>Build and deploy docker container</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<!--<goal>build</goal>-->
|
||||
<goal>push</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<images>
|
||||
<image>
|
||||
<name>${project.artifactId}:${project.version}</name>
|
||||
<registry>${registry.url}</registry>
|
||||
<build>
|
||||
<dockerFile>${project.basedir}/Dockerfile</dockerFile>
|
||||
<assembly>
|
||||
<inline>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.build.finalName}.jar</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/apm-agent/elastic-apm-agent-1.45.0.jar</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/start-credit-tracker.sh</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/credit-tracker.properties</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/monitoring-request.xsd</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/signal-package.xsd</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
</inline>
|
||||
</assembly>
|
||||
<tags>
|
||||
<tag>${project.version}</tag>
|
||||
</tags>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
<build>
|
||||
<finalName>credit-tracker</finalName>
|
||||
<plugins>
|
||||
|
|
@ -181,65 +260,7 @@
|
|||
</includeOnlyProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>0.45.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>Build and deploy docker container</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>push</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<images>
|
||||
<image>
|
||||
<name>${project.artifactId}:${project.version}</name>
|
||||
<registry>${registry.url}</registry>
|
||||
<build>
|
||||
<dockerFile>${project.basedir}/Dockerfile</dockerFile>
|
||||
<assembly>
|
||||
<inline>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.build.finalName}.jar</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/apm-agent/elastic-apm-agent-1.45.0.jar</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/start-credit-tracker.sh</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/credit-tracker.properties</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/monitoring-request.xsd</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${project.basedir}/signal-package.xsd</source>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
</inline>
|
||||
</assembly>
|
||||
<tags>
|
||||
<tag>${project.version}</tag>
|
||||
</tags>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
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 MapperTimingAspect {
|
||||
@Around("within(@org.apache.ibatis.annotations.Mapper *)")
|
||||
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 > 200) {
|
||||
log.warn("Slow mapper call {} took {} ms", method, tookMs);
|
||||
} else {
|
||||
log.debug("Mapper call {} took {} ms", method, tookMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package ru.nbch.credit_tracker.config.rest.filters;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Order(0)
|
||||
public class HttpLoggingFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final Set<String> SENSITIVE_HEADERS = Set.of("authorization", "cookie", "set-cookie");
|
||||
private static final int MAX_BODY = 10 * 1024;
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String path = request.getRequestURI();
|
||||
return path.startsWith("/actuator/health") || path.startsWith("/swagger")
|
||||
|| path.startsWith("/v3/api-docs") || path.startsWith("/static/");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
long startNs = System.nanoTime();
|
||||
|
||||
ContentCachingRequestWrapper req = wrapRequest(request);
|
||||
ContentCachingResponseWrapper resp = new ContentCachingResponseWrapper(response);
|
||||
|
||||
String correlationId = Optional.ofNullable(request.getHeader("X-Correlation-Id"))
|
||||
.filter(StringUtils::hasText)
|
||||
.orElse(UUID.randomUUID().toString());
|
||||
resp.setHeader("X-Correlation-Id", correlationId);
|
||||
|
||||
try {
|
||||
chain.doFilter(req, resp);
|
||||
} finally {
|
||||
long tookMs = (System.nanoTime() - startNs) / 1_000_000;
|
||||
|
||||
String requestBody = readBody(req.getContentAsByteArray(), req.getCharacterEncoding());
|
||||
String responseBody = readBody(resp.getContentAsByteArray(), resp.getCharacterEncoding());
|
||||
|
||||
Map<String, String> reqHeaders = headersMap(req);
|
||||
Map<String, String> respHeaders = headersMap(resp);
|
||||
|
||||
mask(reqHeaders);
|
||||
mask(respHeaders);
|
||||
|
||||
log.info(
|
||||
"HTTP {} {}{} | status={} | took={}ms | cid={} | reqHeaders={} | reqBody={} | respHeaders={} | respBody={}",
|
||||
req.getMethod(),
|
||||
req.getRequestURI(),
|
||||
req.getQueryString() == null ? "" : "?" + req.getQueryString(),
|
||||
resp.getStatus(),
|
||||
tookMs,
|
||||
correlationId,
|
||||
reqHeaders,
|
||||
requestBody,
|
||||
respHeaders,
|
||||
responseBody
|
||||
);
|
||||
|
||||
resp.copyBodyToResponse();
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
|
||||
return (request instanceof ContentCachingRequestWrapper c) ? c : new ContentCachingRequestWrapper(request, MAX_BODY);
|
||||
}
|
||||
|
||||
private static String readBody(byte[] buf, String encoding) {
|
||||
if (buf == null || buf.length == 0) return "";
|
||||
var bytes = buf.length > MAX_BODY ? Arrays.copyOf(buf, MAX_BODY) : buf;
|
||||
String body = new String(bytes, encoding != null ? java.nio.charset.Charset.forName(encoding) : StandardCharsets.UTF_8);
|
||||
if (buf.length > MAX_BODY) body += "…[truncated]";
|
||||
return body;
|
||||
}
|
||||
|
||||
private static Map<String, String> headersMap(HttpServletRequest req) {
|
||||
return Collections.list(req.getHeaderNames()).stream()
|
||||
.collect(Collectors.toMap(h -> h, req::getHeader, (a, b) -> a, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private static Map<String, String> headersMap(HttpServletResponse resp) {
|
||||
return resp.getHeaderNames().stream()
|
||||
.collect(Collectors.toMap(h -> h, resp::getHeader, (a, b) -> a, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private static void mask(Map<String, String> headers) {
|
||||
headers.replaceAll((k, v) -> SENSITIVE_HEADERS.contains(k.toLowerCase()) ? "***" : v);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ public interface PhoneFidMapMapper {
|
|||
List<PhoneFidMap> findFullPhoneFidMap(@Param("packageId") Integer packageId, @Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
void updatePhoneFidMaps(@Param("subjects") List<PhoneFidMap> subjects);
|
||||
void updatePhoneFid(@Param("subject") PhoneFidMap subject);
|
||||
|
||||
void updatePhoneFidMapsErrors(@Param("subjects") List<PhoneFidMap> subjects);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
package ru.nbch.credit_tracker.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.dto.Hit;
|
||||
|
|
@ -22,21 +36,6 @@ import ru.nbch.credit_tracker.service.xml.stax.MonitoringSignalResponseStaxXmlWr
|
|||
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
|
||||
import ru.nbch.credit_tracker.utils.CTUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* Сервис периодического опроса официального сервиса сигналов на наличие новых отчетов по пакетам находящимся в статусе ON_MONITORING
|
||||
*/
|
||||
|
|
@ -60,9 +59,9 @@ public class MonitoringService {
|
|||
}
|
||||
|
||||
public void run() {
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("monitoring-"));
|
||||
long period = config.getMonitoringInterval() != null ? config.getMonitoringInterval() : 15;
|
||||
scheduler.scheduleAtFixedRate(this::monitor, 0, period, TimeUnit.MINUTES);
|
||||
// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("monitoring-"));
|
||||
// long period = config.getMonitoringInterval() != null ? config.getMonitoringInterval() : 15;
|
||||
// scheduler.scheduleAtFixedRate(this::monitor, 0, period, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void monitor() {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ 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.user_request.monitoring_request.MonitoringRequest;
|
||||
import ru.nbch.credit_tracker.enums.Status;
|
||||
import ru.nbch.credit_tracker.mapper.signal.PhoneFidMapMapper;
|
||||
import ru.nbch.credit_tracker.service.indic.FlagsService;
|
||||
import ru.nbch.credit_tracker.service.indic.PersonService;
|
||||
import ru.nbch.credit_tracker.service.indic.PhoneService;
|
||||
|
|
@ -43,6 +44,7 @@ public class SignalService {
|
|||
private final HitsService hitsService;
|
||||
private final UserPackageService userPackageService;
|
||||
private final PhoneFidMapService phoneFidMapService;
|
||||
private final PhoneFidMapMapper phoneFidMapMapper;
|
||||
private final PhoneService phoneService;
|
||||
private final FlagsService flagsService;
|
||||
private final PersonService personService;
|
||||
|
|
@ -54,6 +56,7 @@ public class SignalService {
|
|||
HitsService hitsService,
|
||||
UserPackageService userPackageService,
|
||||
PhoneFidMapService phoneFidMapService,
|
||||
PhoneFidMapMapper mapper,
|
||||
PhoneService phoneService,
|
||||
FlagsService flagsService,
|
||||
PersonService personService,
|
||||
|
|
@ -62,6 +65,7 @@ public class SignalService {
|
|||
this.hitsService = hitsService;
|
||||
this.userPackageService = userPackageService;
|
||||
this.phoneFidMapService = phoneFidMapService;
|
||||
this.phoneFidMapMapper = mapper;
|
||||
this.phoneService = phoneService;
|
||||
this.flagsService = flagsService;
|
||||
this.personService = personService;
|
||||
|
|
@ -125,7 +129,7 @@ public class SignalService {
|
|||
}
|
||||
|
||||
if (!filteredItems.isEmpty()) {
|
||||
boolean fidsFound = updateFlags(filteredItems, packageId);
|
||||
boolean fidsFound = updateFlags2(filteredItems, packageId);
|
||||
if (noFidsFound) {
|
||||
noFidsFound = !fidsFound;
|
||||
}
|
||||
|
|
@ -187,6 +191,26 @@ public class SignalService {
|
|||
return fidsFound;
|
||||
}
|
||||
|
||||
public boolean updateFlags2(List<PhoneFidMap> subjects, Integer packageId) {
|
||||
log.info("Start calculating fids and flags for subjects batch. PackageId: {}", packageId);
|
||||
Map<Integer, PhoneFidMap> fidsMap = phoneService.defineFidByPhone(subjects);
|
||||
Map<Integer, FlagsService.FlagsData> flagsDataMap = flagsService.findFlags(fidsMap.keySet().stream().toList());
|
||||
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);
|
||||
phoneFidMap.setFlagDebt5000(flagsData.getFlagDebt5000());
|
||||
phoneFidMap.setFlag90days(flagsData.getFlag90days());
|
||||
phoneFidMap.setCalculatedAt(LocalDateTime.now());
|
||||
|
||||
phoneFidMapMapper.updatePhoneFid(phoneFidMap);
|
||||
}
|
||||
log.info("Finish calculating fids and flags for subjects. PackageId: {}. Batch size: {}", packageId, subjects.size());
|
||||
return !fidsMap.isEmpty();
|
||||
}
|
||||
|
||||
public void rejectPackage(MonitoringRequest request) {
|
||||
UserPackage userPackage = UserPackage.builder()
|
||||
.userId(request.getUserId())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package ru.nbch.credit_tracker.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.dto.PhoneFidMap;
|
||||
|
|
@ -9,14 +12,6 @@ import ru.nbch.credit_tracker.entities.dto.UserPackage;
|
|||
import ru.nbch.credit_tracker.enums.Status;
|
||||
import ru.nbch.credit_tracker.service.signal.SignalRestService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Сервис периодического обновления фидов и флагов просрочек по пакетам находящимся в статусе ON_MONITORING
|
||||
* По обновленным пакетам формируется новый запрос постановки на мониторинг в официальный сервис сигналов
|
||||
|
|
@ -43,8 +38,8 @@ public class UpdatingFlagsService {
|
|||
}
|
||||
|
||||
public void run() {
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("updating-flags-"));
|
||||
scheduler.scheduleAtFixedRate(this::update, 0, updateFlagsInterval, TimeUnit.DAYS);
|
||||
// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new CustomizableThreadFactory("updating-flags-"));
|
||||
// scheduler.scheduleAtFixedRate(this::update, 0, updateFlagsInterval, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
package ru.nbch.credit_tracker.service.indic;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.nbch.credit_tracker.mapper.indic.FlagsMapper;
|
||||
|
||||
@Service
|
||||
public class FlagsService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FlagsService.class);
|
||||
private final FlagsMapper mapper;
|
||||
|
||||
public FlagsService(FlagsMapper mapper) {
|
||||
|
|
@ -25,11 +26,13 @@ public class FlagsService {
|
|||
if (fids.isEmpty()) {
|
||||
return 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);
|
||||
List<Integer> distinctFids = fids.stream().distinct().toList();
|
||||
log.debug("Fids count: {}", distinctFids.size());
|
||||
Map<Integer, Integer> flags5000 = calcFlag5000(distinctFids);
|
||||
Map<Integer, Integer> flags90DaysByPastDueArrear = calcFlag90DaysByPastDueArrear(distinctFids);
|
||||
Map<Integer, Integer> flags90DaysByRuPaymt = calcFlag90DaysByRuPaymt(distinctFids);
|
||||
|
||||
Map<Integer, FlagsData> result = new HashMap<>();
|
||||
fids.forEach(fid -> {
|
||||
Integer flagDebt5000 = flags5000.get(fid);
|
||||
if (flagDebt5000 == null) {
|
||||
|
|
@ -55,15 +58,11 @@ public class FlagsService {
|
|||
* Если ни по одному счёту условие не выполняется или счетов вообще не найдено, флаг = 0.
|
||||
*/
|
||||
private Map<Integer, Integer> calcFlag5000(List<Integer> fids) {
|
||||
return mapper.findFlagsDebt5000(fids).stream()
|
||||
.collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
|
||||
(existing, replacement) -> {
|
||||
if (existing == 1) {
|
||||
return existing;
|
||||
} else {
|
||||
return replacement;
|
||||
}
|
||||
}));
|
||||
log.debug("Start calculating flag 5000");
|
||||
List<FlagFidData> rows = mapper.findFlagsDebt5000(fids);
|
||||
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows);
|
||||
log.debug("Complete calculating flag 5000");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,31 +71,33 @@ public class FlagsService {
|
|||
* Если ни по одной записи условия не выполняются или записей вообще не найдено, флаг = 0.
|
||||
*/
|
||||
private Map<Integer, Integer> calcFlag90DaysByPastDueArrear(List<Integer> fids) {
|
||||
log.debug("Start calculating flag 90 by PastDueArrear");
|
||||
LocalDate yearAgo = LocalDate.now().minusYears(1);
|
||||
return mapper.findFlag90ByPastDueArrear(fids, yearAgo).stream()
|
||||
.collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
|
||||
(existing, replacement) -> {
|
||||
if (existing == 1) {
|
||||
return existing;
|
||||
} else {
|
||||
return replacement;
|
||||
}
|
||||
}));
|
||||
List<FlagFidData> rows = mapper.findFlag90ByPastDueArrear(fids, yearAgo);
|
||||
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows);
|
||||
log.debug("Complete calculating flag 90 by PastDueArrear");
|
||||
return res;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> calcFlag90DaysByRuPaymt(List<Integer> fids) {
|
||||
log.debug("Start calculating flag 90 by ruPaymt");
|
||||
LocalDate yearAgo = LocalDate.now().minusYears(1);
|
||||
return mapper.findFlag90ByRuPaymt(fids, yearAgo).stream()
|
||||
.collect(Collectors.toMap(FlagFidData::getFid, FlagFidData::getFlag,
|
||||
(existing, replacement) -> {
|
||||
if (existing == 1) {
|
||||
return existing;
|
||||
} else {
|
||||
return replacement;
|
||||
}
|
||||
}));
|
||||
List<FlagFidData> rows = mapper.findFlag90ByRuPaymt(fids, yearAgo);
|
||||
Map<Integer, Integer> res = makeFidFlagMapByRs(fids, rows);
|
||||
log.debug("Complete calculating flag 90 by ruPaymt");
|
||||
return res;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> makeFidFlagMapByRs(List<Integer> fids, List<FlagFidData> rows) {
|
||||
Map<Integer, Integer> result = new HashMap<>();
|
||||
for (Integer fid : fids) {
|
||||
result.put(fid, 0);
|
||||
}
|
||||
for (FlagFidData row : rows) {
|
||||
result.put(row.getFid(), row.getFlag() != null ? row.getFlag() : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
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.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
|
||||
import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
|
||||
|
||||
@Service
|
||||
public class PhoneService {
|
||||
|
|
@ -31,6 +33,37 @@ public class PhoneService {
|
|||
return convertFidsDataToMap(mapper.findFids(phones));
|
||||
}
|
||||
|
||||
public Map<Integer, PhoneFidMap> defineFidByPhone(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()
|
||||
)
|
||||
);
|
||||
|
||||
//отсортированный список по NUMBER, FID и FILE_SINCE_DT
|
||||
List<FidsData> phonesByFid = mapper.findFids(phones);
|
||||
Iterator<FidsData> iterator = phonesByFid.iterator();
|
||||
for (PhoneFidMap phoneFid : subjects) {
|
||||
while (iterator.hasNext()) {
|
||||
FidsData fidsData = iterator.next();
|
||||
if (fidsData.getPhone().equals(phoneFid.getPhone())) {
|
||||
//fixme доступ по ссылке, страшно, придумать что-то еще
|
||||
phoneFidMap.put(fidsData.getFid(), phoneFid);
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return phoneFidMap;
|
||||
}
|
||||
|
||||
private Map<String, Integer> convertFidsDataToMap(List<FidsData> fidsData) {
|
||||
if (fidsData == null || fidsData.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
|
|
@ -50,6 +83,8 @@ public class PhoneService {
|
|||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class FidsData {
|
||||
private Integer fid;
|
||||
private String phone;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
|
|
|||
|
|
@ -11,70 +11,70 @@
|
|||
</resultMap>
|
||||
|
||||
<select id="findFlagsDebt5000" resultMap="FlagsFidDataResultMap">
|
||||
SELECT rp.FID as fid,
|
||||
CASE
|
||||
WHEN EXISTS (SELECT 1
|
||||
FROM RU_PASTDUEARREAR rp2
|
||||
WHERE rp2.FID = rp.FID
|
||||
AND rp2.ACC_SERIAL_NUM = rp.ACC_SERIAL_NUM
|
||||
AND rp2.REPORTED_DT = rp.MAX_REP_DATE
|
||||
AND rp2.AMT_PAST_DUE > 5000) THEN 1
|
||||
ELSE 0
|
||||
END as flag
|
||||
FROM (SELECT FID,
|
||||
ACC_SERIAL_NUM,
|
||||
MAX(REPORTED_DT) as MAX_REP_DATE
|
||||
WITH latest AS (
|
||||
SELECT
|
||||
fid,
|
||||
acc_serial_num,
|
||||
amt_past_due,
|
||||
ROW_NUMBER() OVER (PARTITION BY fid, acc_serial_num ORDER BY reported_dt DESC) AS rn
|
||||
FROM RU_PASTDUEARREAR
|
||||
WHERE FID IN
|
||||
WHERE fid IN
|
||||
<foreach item="fid" collection="fids" open="(" separator="," close=")">
|
||||
#{fid}
|
||||
</foreach>
|
||||
GROUP BY FID, ACC_SERIAL_NUM) rp
|
||||
GROUP BY rp.FID, rp.ACC_SERIAL_NUM, rp.MAX_REP_DATE
|
||||
SELECT
|
||||
fid,
|
||||
CASE WHEN SUM(CASE WHEN amt_past_due > 5000 THEN 1 ELSE 0 END) > 0
|
||||
THEN 1 ELSE 0
|
||||
END AS flag
|
||||
FROM latest WHERE rn = 1
|
||||
GROUP BY fid
|
||||
WITH UR
|
||||
</select>
|
||||
|
||||
<select id="findFlag90ByPastDueArrear" resultMap="FlagsFidDataResultMap">
|
||||
SELECT rp.FID as fid,
|
||||
CASE
|
||||
WHEN EXISTS (SELECT 1
|
||||
FROM RU_PASTDUEARREAR rp2
|
||||
WHERE rp2.FID = rp.FID
|
||||
AND rp2.REPORTED_DT >= #{reportedDate}
|
||||
AND rp2.DAYS_PAST_DUE > 90) THEN 1
|
||||
ELSE 0
|
||||
END as flag
|
||||
FROM (SELECT FID,
|
||||
DAYS_PAST_DUE
|
||||
FROM RU_PASTDUEARREAR
|
||||
WHERE FID IN
|
||||
WITH latest AS (
|
||||
SELECT
|
||||
r.fid,
|
||||
r.days_past_due,
|
||||
ROW_NUMBER() OVER (PARTITION BY r.fid ORDER BY r.reported_dt DESC) AS rn
|
||||
FROM RU_PASTDUEARREAR r
|
||||
WHERE r.fid IN
|
||||
<foreach item="fid" collection="fids" open="(" separator="," close=")">
|
||||
#{fid}
|
||||
</foreach>
|
||||
GROUP BY FID, DAYS_PAST_DUE) rp
|
||||
GROUP BY rp.FID, rp.DAYS_PAST_DUE
|
||||
AND r.reported_dt >= #{reportedDate}
|
||||
)
|
||||
SELECT
|
||||
fid,
|
||||
CASE WHEN MAX(CASE WHEN days_past_due > 90 THEN 1 ELSE 0 END) = 1
|
||||
THEN 1 ELSE 0
|
||||
END AS flag
|
||||
FROM latest WHERE rn = 1
|
||||
GROUP BY fid
|
||||
WITH UR
|
||||
</select>
|
||||
|
||||
<select id="findFlag90ByRuPaymt" resultMap="FlagsFidDataResultMap">
|
||||
SELECT rp.FID as fid,
|
||||
CASE
|
||||
WHEN EXISTS (SELECT 1
|
||||
FROM RU_PAYMT rp2
|
||||
WHERE rp2.FID = rp.FID
|
||||
AND rp2.REPORTED_DT >= #{reportedDate}
|
||||
AND rp2.DAYS_PAST_DUE > 90) THEN 1
|
||||
ELSE 0
|
||||
END as flag
|
||||
FROM (SELECT FID,
|
||||
DAYS_PAST_DUE
|
||||
FROM RU_PAYMT
|
||||
WHERE FID IN
|
||||
WITH latest AS (
|
||||
SELECT
|
||||
r.fid,
|
||||
r.days_past_due,
|
||||
ROW_NUMBER() OVER (PARTITION BY r.fid ORDER BY r.reported_dt DESC) AS rn
|
||||
FROM RU_PAYMT r
|
||||
WHERE r.fid IN
|
||||
<foreach item="fid" collection="fids" open="(" separator="," close=")">
|
||||
#{fid}
|
||||
</foreach>
|
||||
GROUP BY FID, DAYS_PAST_DUE) rp
|
||||
GROUP BY rp.FID, rp.DAYS_PAST_DUE
|
||||
AND r.reported_dt >= #{reportedDate}
|
||||
)
|
||||
SELECT
|
||||
fid,
|
||||
CASE WHEN MAX(CASE WHEN days_past_due > 90 THEN 1 ELSE 0 END) = 1
|
||||
THEN 1 ELSE 0
|
||||
END AS flag
|
||||
FROM latest WHERE rn = 1
|
||||
GROUP BY fid
|
||||
WITH UR
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -12,14 +12,21 @@
|
|||
</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
|
||||
SELECT
|
||||
pn.number,
|
||||
pn.fid,
|
||||
pn.file_since_dt
|
||||
FROM indic.PHONE_NORM pn
|
||||
JOIN (SELECT DISTINCT fid FROM indic.NAME) nf
|
||||
ON nf.fid = pn.fid
|
||||
WHERE number in
|
||||
<foreach item="phone" collection="phones" open="(" separator="," close=")">
|
||||
#{phone}
|
||||
</foreach>
|
||||
ORDER BY fileSinceDate DESC, fid DESC
|
||||
ORDER BY
|
||||
pn.number ASC,
|
||||
pn.file_since_dt DESC,
|
||||
pn.fid DESC
|
||||
WITH UR
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -42,6 +42,17 @@
|
|||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="updatePhoneFid" parameterType="ru.nbch.credit_tracker.entities.dto.PhoneFidMap">
|
||||
UPDATE signals_phone_fid_map
|
||||
SET
|
||||
fid = #{subject.fid},
|
||||
flag_debt5000 = #{subject.flagDebt5000},
|
||||
flag_90_12 = #{subject.flag90days},
|
||||
calculated_at = #{subject.calculatedAt},
|
||||
error_text = #{subject.errorText}
|
||||
WHERE id = #{subject.id}
|
||||
</update>
|
||||
|
||||
<update id="updatePhoneFidMapsErrors" parameterType="java.util.List">
|
||||
<foreach collection="subjects" item="subject" separator=";">
|
||||
UPDATE signals_phone_fid_map
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
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 org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import static org.mockito.Mockito.when;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
|
||||
import ru.nbch.credit_tracker.mapper.indic.PhoneMapper;
|
||||
import ru.nbch.credit_tracker.service.indic.PhoneService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class PhoneServiceTest {
|
||||
|
||||
@Mock
|
||||
PhoneMapper phoneMapper;
|
||||
|
||||
@Test
|
||||
public void defineFidByPhoneTest() {
|
||||
PhoneService phoneService = new PhoneService(phoneMapper);
|
||||
|
||||
fidsDataWithEqualSinceDt.sort(byDbOrder);
|
||||
when(phoneMapper.findFids(Mockito.anyList())).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("1114").build());
|
||||
|
||||
Map<Integer, PhoneFidMap> res = phoneService.defineFidByPhone(phoneFidMap);
|
||||
Assertions.assertEquals(3, res.size());
|
||||
Assertions.assertEquals("1111", res.get(124).getPhone());
|
||||
Assertions.assertEquals("1112", res.get(125).getPhone());
|
||||
Assertions.assertEquals("1114", res.get(126).getPhone());
|
||||
}
|
||||
|
||||
private final List<PhoneService.FidsData> fidsDataWithEqualSinceDt = new ArrayList<>(
|
||||
List.of(
|
||||
new PhoneService.FidsData(123, "1111", LocalDate.of(2025, 9, 10)),
|
||||
new PhoneService.FidsData(124, "1111", LocalDate.of(2025, 9, 10)),
|
||||
new PhoneService.FidsData(125, "1112", LocalDate.of(2024, 10, 10)),
|
||||
new PhoneService.FidsData(126, "1114", LocalDate.of(2024, 10, 11))
|
||||
)
|
||||
);
|
||||
|
||||
Comparator<PhoneService.FidsData> byDbOrder =
|
||||
Comparator.comparing(PhoneService.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
|
||||
.thenComparing(PhoneService.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
|
||||
.thenComparing(PhoneService.FidsData::getFid, Comparator.reverseOrder());
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue