async jobs added
This commit is contained in:
parent
cb2b3dddef
commit
4eab19e87e
10 changed files with 448 additions and 142 deletions
|
|
@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
|
|
@ -24,6 +25,7 @@ import ru.nbch.indicsearchservice.dto.request.AddSearchJobRequestDto;
|
||||||
import ru.nbch.indicsearchservice.dto.response.AddSearchJobResponseDto;
|
import ru.nbch.indicsearchservice.dto.response.AddSearchJobResponseDto;
|
||||||
import ru.nbch.indicsearchservice.dto.response.JobStatusResponseDto;
|
import ru.nbch.indicsearchservice.dto.response.JobStatusResponseDto;
|
||||||
import ru.nbch.indicsearchservice.exception.InternalException;
|
import ru.nbch.indicsearchservice.exception.InternalException;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.ExportType;
|
||||||
import ru.nbch.indicsearchservice.service.IndicSearchService;
|
import ru.nbch.indicsearchservice.service.IndicSearchService;
|
||||||
import ru.nbch.indicsearchservice.service.SettingsService;
|
import ru.nbch.indicsearchservice.service.SettingsService;
|
||||||
|
|
||||||
|
|
@ -63,7 +65,7 @@ public class IndicSearchController {
|
||||||
description = "Success")
|
description = "Success")
|
||||||
})
|
})
|
||||||
@GetMapping("/job_status/{id}")
|
@GetMapping("/job_status/{id}")
|
||||||
public JobStatusResponseDto getJobStatus(@PathVariable("id") Long id) {
|
public JobStatusResponseDto getJobStatus(@PathVariable("id") UUID id) {
|
||||||
return indicSearchService.getJobStatus(id);
|
return indicSearchService.getJobStatus(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,8 +79,8 @@ public class IndicSearchController {
|
||||||
description = "Success")
|
description = "Success")
|
||||||
})
|
})
|
||||||
@GetMapping("/export/{id}")
|
@GetMapping("/export/{id}")
|
||||||
public ResponseEntity<?> export(@PathVariable("id") Long id,
|
public ResponseEntity<?> export(@PathVariable("id") UUID id,
|
||||||
@RequestParam(name = "type", defaultValue = "JSON") String exportType) {
|
@RequestParam(name = "type", defaultValue = "JSON") ExportType exportType) {
|
||||||
return indicSearchService.export(id, exportType);
|
return indicSearchService.export(id, exportType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,26 @@ package ru.nbch.indicsearchservice.dto.response;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Schema(description = "Add search job response data transfer object")
|
@Schema(description = "Add search job response data transfer object")
|
||||||
public class AddSearchJobResponseDto {
|
public class AddSearchJobResponseDto {
|
||||||
@Schema(description = "Job id", example = "1")
|
@Schema(description = "Job id", example = "63e6dcb5-c5bd-4ce6-adb1-947ef2a2f22c")
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long jobId;
|
private UUID jobId;
|
||||||
|
|
||||||
public AddSearchJobResponseDto() {
|
public AddSearchJobResponseDto() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddSearchJobResponseDto(Long jobId) {
|
public AddSearchJobResponseDto(UUID jobId) {
|
||||||
this.jobId = jobId;
|
this.jobId = jobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getJobId() {
|
public UUID getJobId() {
|
||||||
return jobId;
|
return jobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJobId(Long jobId) {
|
public void setJobId(UUID jobId) {
|
||||||
this.jobId = jobId;
|
this.jobId = jobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@ public class ExcelExporter implements Exporter {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeExcelFile(@Nullable Path aggregatedFile, UUID taskId, Long totalRows, Map<String, TableMetaData> tableMetaDataMap) {
|
public Path writeExcelFile(@Nullable Path aggregatedFile, UUID taskId, Long totalRows, Map<String, TableMetaData> tableMetaDataMap) {
|
||||||
if (aggregatedFile == null) {
|
if (aggregatedFile == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path excelFile = settings.getStorageDirectory().toPath().resolve(taskId.toString() + ".xlsx");
|
Path excelFile = settings.getStorageDirectory().toPath().resolve(taskId.toString() + ".xlsx");
|
||||||
|
|
@ -93,6 +93,8 @@ public class ExcelExporter implements Exporter {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return excelFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToExcel(Workbook workbook,
|
private void writeToExcel(Workbook workbook,
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ public class JsonExporter implements Exporter {
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeJsonFile(@Nullable Path aggregatedFile,
|
public Path writeJsonFile(@Nullable Path aggregatedFile,
|
||||||
UUID taskId,
|
UUID taskId,
|
||||||
SearchParameter searchParameter,
|
SearchParameter searchParameter,
|
||||||
Map<String, TableMetaData> tableMetaDataMap) {
|
Map<String, TableMetaData> tableMetaDataMap) {
|
||||||
if (aggregatedFile == null) {
|
if (aggregatedFile == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPassportOgrnFid = searchParameter == SearchParameter.PASSPORT ||
|
boolean isPassportOgrnFid = searchParameter == SearchParameter.PASSPORT ||
|
||||||
|
|
@ -122,6 +122,8 @@ public class JsonExporter implements Exporter {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return jsonFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableData buildTableDataHierarchy(List<String> rows, Map<String, TableMetaData> tableMetaDataMap) {
|
private TableData buildTableDataHierarchy(List<String> rows, Map<String, TableMetaData> tableMetaDataMap) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ru.nbch.indicsearchservice.manager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.nbch.indicsearchservice.dto.request.AddSearchJobRequestDto;
|
||||||
|
import ru.nbch.indicsearchservice.model.SearchJob;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.ExportType;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.JobStatus;
|
||||||
|
import ru.nbch.indicsearchservice.processor.IndicSearchProcessor;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class IndicSearchJobManager {
|
||||||
|
private final ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||||
|
private final Map<UUID, SearchJob> searchJobs = new ConcurrentHashMap<>();
|
||||||
|
private final IndicSearchProcessor indicSearchProcessor;
|
||||||
|
|
||||||
|
public IndicSearchJobManager(IndicSearchProcessor indicSearchProcessor) {
|
||||||
|
this.indicSearchProcessor = indicSearchProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID addSearchJob(AddSearchJobRequestDto request) {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
SearchJob newSearchJob = new SearchJob(
|
||||||
|
uuid,
|
||||||
|
request.getDataType(),
|
||||||
|
request.getSearchParameter(),
|
||||||
|
request.getSearchValue(),
|
||||||
|
request.getMemberCode()
|
||||||
|
);
|
||||||
|
searchJobs.put(uuid, newSearchJob);
|
||||||
|
executor.submit(() -> indicSearchProcessor.aggregateTables(newSearchJob));
|
||||||
|
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobStatus getJobStatus(UUID id) {
|
||||||
|
SearchJob searchJob = searchJobs.get(id);
|
||||||
|
if (searchJob != null) {
|
||||||
|
return searchJob.getJobStatus();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseEntity<?> export(UUID id, ExportType exportType) {
|
||||||
|
SearchJob searchJob = searchJobs.get(id);
|
||||||
|
if (searchJob == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return indicSearchProcessor.export(searchJob, exportType);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/main/java/ru/nbch/indicsearchservice/model/SearchJob.java
Normal file
134
src/main/java/ru/nbch/indicsearchservice/model/SearchJob.java
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
package ru.nbch.indicsearchservice.model;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.DataType;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.JobStatus;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.SearchParameter;
|
||||||
|
|
||||||
|
public class SearchJob {
|
||||||
|
private final UUID uuid;
|
||||||
|
private final DataType dataType;
|
||||||
|
private final SearchParameter searchParameter;
|
||||||
|
private final String searchValue;
|
||||||
|
private final String memberCode;
|
||||||
|
|
||||||
|
private JobStatus jobStatus;
|
||||||
|
private Path aggregatedFilePath;
|
||||||
|
private Long rowCount;
|
||||||
|
private Map<String, TableMetaData> tableMetaDataMap;
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime finishedAt;
|
||||||
|
|
||||||
|
public SearchJob(UUID uuid, DataType dataType, SearchParameter searchParameter, String searchValue, String memberCode) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.dataType = dataType;
|
||||||
|
this.searchParameter = searchParameter;
|
||||||
|
this.searchValue = searchValue;
|
||||||
|
this.memberCode = memberCode;
|
||||||
|
|
||||||
|
this.setJobStatus(JobStatus.QUEUED);
|
||||||
|
this.createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataType getDataType() {
|
||||||
|
return dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchParameter getSearchParameter() {
|
||||||
|
return searchParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSearchValue() {
|
||||||
|
return searchValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMemberCode() {
|
||||||
|
return memberCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobStatus getJobStatus() {
|
||||||
|
return jobStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJobStatus(JobStatus jobStatus) {
|
||||||
|
this.jobStatus = jobStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getAggregatedFilePath() {
|
||||||
|
return aggregatedFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAggregatedFilePath(Path aggregatedFilePath) {
|
||||||
|
this.aggregatedFilePath = aggregatedFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRowCount() {
|
||||||
|
return rowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRowCount(Long rowCount) {
|
||||||
|
this.rowCount = rowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, TableMetaData> getTableMetaDataMap() {
|
||||||
|
return tableMetaDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableMetaDataMap(Map<String, TableMetaData> tableMetaDataMap) {
|
||||||
|
this.tableMetaDataMap = tableMetaDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getFinishedAt() {
|
||||||
|
return finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinishedAt(LocalDateTime finishedAt) {
|
||||||
|
this.finishedAt = finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SearchJob searchJob = (SearchJob) o;
|
||||||
|
return Objects.equals(uuid, searchJob.uuid) && dataType == searchJob.dataType && searchParameter == searchJob.searchParameter && Objects.equals(searchValue, searchJob.searchValue) && Objects.equals(memberCode, searchJob.memberCode) && jobStatus == searchJob.jobStatus && Objects.equals(aggregatedFilePath, searchJob.aggregatedFilePath) && Objects.equals(rowCount, searchJob.rowCount) && Objects.equals(tableMetaDataMap, searchJob.tableMetaDataMap) && Objects.equals(createdAt, searchJob.createdAt) && Objects.equals(finishedAt, searchJob.finishedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(uuid, dataType, searchParameter, searchValue, memberCode, jobStatus, aggregatedFilePath, rowCount, tableMetaDataMap, createdAt, finishedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SearchJob{" +
|
||||||
|
"uuid=" + uuid +
|
||||||
|
", dataType=" + dataType +
|
||||||
|
", searchParameter=" + searchParameter +
|
||||||
|
", searchValue='" + searchValue + '\'' +
|
||||||
|
", memberCode='" + memberCode + '\'' +
|
||||||
|
", jobStatus=" + jobStatus +
|
||||||
|
", filePath=" + aggregatedFilePath +
|
||||||
|
", rowCount=" + rowCount +
|
||||||
|
", tableMetaDataMap=" + tableMetaDataMap +
|
||||||
|
", createdAt=" + createdAt +
|
||||||
|
", finishedAt=" + finishedAt +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package ru.nbch.indicsearchservice.model.enums;
|
||||||
|
|
||||||
|
public enum ExportType {
|
||||||
|
XLSX,
|
||||||
|
JSON
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
package ru.nbch.indicsearchservice.processor;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
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.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import org.apache.commons.lang3.mutable.MutableLong;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.http.ContentDisposition;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
|
import ru.nbch.indicsearchservice.aggregator.FileAggregator;
|
||||||
|
import ru.nbch.indicsearchservice.exception.InternalException;
|
||||||
|
import ru.nbch.indicsearchservice.exporter.ExcelExporter;
|
||||||
|
import ru.nbch.indicsearchservice.exporter.JsonExporter;
|
||||||
|
import ru.nbch.indicsearchservice.loader.FirstLevelTableLoader;
|
||||||
|
import ru.nbch.indicsearchservice.model.SearchJob;
|
||||||
|
import ru.nbch.indicsearchservice.model.TableMetaData;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.ExportType;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.JobStatus;
|
||||||
|
import ru.nbch.indicsearchservice.model.enums.SearchParameter;
|
||||||
|
import ru.nbch.indicsearchservice.util.TableMetaDataUtil;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class IndicSearchProcessor {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(IndicSearchProcessor.class);
|
||||||
|
|
||||||
|
private final List<FirstLevelTableLoader> tableLoaders;
|
||||||
|
private final List<FileAggregator> fileAggregators;
|
||||||
|
private final ExcelExporter excelExporter;
|
||||||
|
private final JsonExporter jsonExporter;
|
||||||
|
private final TableMetaDataUtil tableMetaDataUtil;
|
||||||
|
|
||||||
|
public IndicSearchProcessor(List<FirstLevelTableLoader> tableLoaders,
|
||||||
|
List<FileAggregator> fileAggregators,
|
||||||
|
ExcelExporter excelExporter,
|
||||||
|
JsonExporter jsonExporter,
|
||||||
|
TableMetaDataUtil tableMetaDataUtil) {
|
||||||
|
this.tableLoaders = tableLoaders;
|
||||||
|
this.fileAggregators = fileAggregators;
|
||||||
|
this.excelExporter = excelExporter;
|
||||||
|
this.jsonExporter = jsonExporter;
|
||||||
|
this.tableMetaDataUtil = tableMetaDataUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void aggregateTables(SearchJob job) {
|
||||||
|
job.setJobStatus(JobStatus.IN_PROGRESS);
|
||||||
|
|
||||||
|
StopWatch sw = new StopWatch();
|
||||||
|
|
||||||
|
// Подготовительный этап. Получение мета данных из БД
|
||||||
|
log.info("Prep step started. Getting metadata from database");
|
||||||
|
sw.start();
|
||||||
|
|
||||||
|
Map<String, TableMetaData> tableMetaDataMap = tableMetaDataUtil.prepareTableMetaData(job.getDataType(), job.getSearchParameter());
|
||||||
|
job.setTableMetaDataMap(tableMetaDataMap);
|
||||||
|
|
||||||
|
sw.stop();
|
||||||
|
log.info(sw.prettyPrint());
|
||||||
|
sw = new StopWatch();
|
||||||
|
|
||||||
|
// Первый этап. Получение данных из БД и заполнение файлов
|
||||||
|
log.info("First step started. Getting data from database and filling csv files");
|
||||||
|
sw.start();
|
||||||
|
|
||||||
|
Map<String, Path> tableFileMap = new ConcurrentHashMap<>();
|
||||||
|
tableLoaders.parallelStream().forEach(tl -> tl.searchBy(
|
||||||
|
job.getUuid(),
|
||||||
|
job.getDataType(),
|
||||||
|
job.getSearchParameter(),
|
||||||
|
job.getSearchValue(),
|
||||||
|
job.getMemberCode().substring(0, 6),
|
||||||
|
tableFileMap,
|
||||||
|
tableMetaDataMap
|
||||||
|
));
|
||||||
|
|
||||||
|
sw.stop();
|
||||||
|
log.info(sw.prettyPrint());
|
||||||
|
sw = new StopWatch();
|
||||||
|
|
||||||
|
// Второй этап. Объединение файлов в один
|
||||||
|
log.info("Second step started. Aggregating table files into one.");
|
||||||
|
sw.start();
|
||||||
|
|
||||||
|
MutableLong rowCounter = new MutableLong();
|
||||||
|
Path aggregatedFile = fileAggregators.stream().map(fa -> fa.aggregateFiles(
|
||||||
|
job.getUuid(),
|
||||||
|
job.getDataType(),
|
||||||
|
job.getSearchParameter(),
|
||||||
|
job.getSearchValue(),
|
||||||
|
job.getMemberCode().substring(0, 6),
|
||||||
|
tableFileMap,
|
||||||
|
rowCounter))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
job.setAggregatedFilePath(aggregatedFile);
|
||||||
|
job.setRowCount(rowCounter.longValue());
|
||||||
|
job.setFinishedAt(LocalDateTime.now());
|
||||||
|
job.setJobStatus(JobStatus.DONE);
|
||||||
|
|
||||||
|
sw.stop();
|
||||||
|
log.info(sw.prettyPrint());
|
||||||
|
|
||||||
|
for (Path tableFilePath : tableFileMap.values()) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(tableFilePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalException("Failed to delete file " + tableFilePath + " " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseEntity<?> export(SearchJob job, ExportType exportType) {
|
||||||
|
StopWatch sw = new StopWatch();
|
||||||
|
sw.start();
|
||||||
|
|
||||||
|
UUID taskId = job.getUuid();
|
||||||
|
SearchParameter searchParameter = job.getSearchParameter();
|
||||||
|
|
||||||
|
Path aggregatedFile = job.getAggregatedFilePath();
|
||||||
|
|
||||||
|
long rowCount = job.getRowCount();
|
||||||
|
Map<String, TableMetaData> tableMetaDataMap = job.getTableMetaDataMap();
|
||||||
|
|
||||||
|
Path exportedFile;
|
||||||
|
String fileName = "err.txt";
|
||||||
|
if (exportType == ExportType.JSON) {
|
||||||
|
fileName = "output.json";
|
||||||
|
exportedFile = jsonExporter.writeJsonFile(aggregatedFile, taskId, searchParameter, tableMetaDataMap);
|
||||||
|
} else if (exportType == ExportType.XLSX) {
|
||||||
|
fileName = "NBKI table.xlsx";
|
||||||
|
tableMetaDataUtil.fillTableMetaDataWithLocalization(tableMetaDataMap);
|
||||||
|
exportedFile = excelExporter.writeExcelFile(aggregatedFile, taskId, rowCount, tableMetaDataMap);
|
||||||
|
} else {
|
||||||
|
exportedFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// long rowLimit = Long.parseLong(settingsService.getSettingsValueByName("LimitDB"));
|
||||||
|
// if (rowCount < rowLimit && !IS_EXPORT_AS_EXCEL) {
|
||||||
|
// jsonExporter.writeJsonFile(aggregatedFile, taskId, searchParameter, tableMetaDataMap);
|
||||||
|
// } else {
|
||||||
|
// tableMetaDataUtil.fillTableMetaDataWithLocalization(tableMetaDataMap);
|
||||||
|
// excelExporter.writeExcelFile(aggregatedFile, taskId, rowCount, tableMetaDataMap);
|
||||||
|
// }
|
||||||
|
|
||||||
|
sw.stop();
|
||||||
|
log.info(sw.prettyPrint());
|
||||||
|
|
||||||
|
if (exportedFile == null) {
|
||||||
|
return ResponseEntity.internalServerError().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
long contentLength;
|
||||||
|
try {
|
||||||
|
contentLength = Files.size(exportedFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to get temp excel file size!", e);
|
||||||
|
try {
|
||||||
|
if (aggregatedFile != null) {
|
||||||
|
Files.deleteIfExists(aggregatedFile);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(exportedFile);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
throw new InternalException("Failed to get exported file size! " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStreamResource exportedFileResource;
|
||||||
|
try {
|
||||||
|
exportedFileResource = new InputStreamResource(new FileInputStream(exportedFile.toFile()) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
if (aggregatedFile != null) {
|
||||||
|
Files.deleteIfExists(aggregatedFile);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(exportedFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to create exported file resource!", e);
|
||||||
|
try {
|
||||||
|
if (aggregatedFile != null) {
|
||||||
|
Files.deleteIfExists(aggregatedFile);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(exportedFile);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
throw new InternalException("Failed to create exported file resource! " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.contentLength(contentLength)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
ContentDisposition.attachment()
|
||||||
|
.filename(fileName, StandardCharsets.UTF_8)
|
||||||
|
.build()
|
||||||
|
.toString())
|
||||||
|
.body(exportedFileResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,151 +1,36 @@
|
||||||
package ru.nbch.indicsearchservice.service;
|
package ru.nbch.indicsearchservice.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import org.apache.commons.lang3.mutable.MutableLong;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StopWatch;
|
|
||||||
import ru.nbch.indicsearchservice.aggregator.FileAggregator;
|
|
||||||
import ru.nbch.indicsearchservice.dto.request.AddSearchJobRequestDto;
|
import ru.nbch.indicsearchservice.dto.request.AddSearchJobRequestDto;
|
||||||
import ru.nbch.indicsearchservice.dto.response.AddSearchJobResponseDto;
|
import ru.nbch.indicsearchservice.dto.response.AddSearchJobResponseDto;
|
||||||
import ru.nbch.indicsearchservice.dto.response.JobStatusResponseDto;
|
import ru.nbch.indicsearchservice.dto.response.JobStatusResponseDto;
|
||||||
import ru.nbch.indicsearchservice.exception.InternalException;
|
import ru.nbch.indicsearchservice.manager.IndicSearchJobManager;
|
||||||
import ru.nbch.indicsearchservice.exporter.ExcelExporter;
|
import ru.nbch.indicsearchservice.model.enums.ExportType;
|
||||||
import ru.nbch.indicsearchservice.exporter.JsonExporter;
|
import ru.nbch.indicsearchservice.model.enums.JobStatus;
|
||||||
import ru.nbch.indicsearchservice.loader.FirstLevelTableLoader;
|
|
||||||
import ru.nbch.indicsearchservice.model.TableMetaData;
|
|
||||||
import ru.nbch.indicsearchservice.util.TableMetaDataUtil;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class IndicSearchService {
|
public class IndicSearchService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(IndicSearchService.class);
|
private final IndicSearchJobManager indicSearchJobManager;
|
||||||
|
|
||||||
private static final boolean IS_EXPORT_AS_EXCEL = false;
|
public IndicSearchService(IndicSearchJobManager indicSearchJobManager) {
|
||||||
|
this.indicSearchJobManager = indicSearchJobManager;
|
||||||
private final List<FirstLevelTableLoader> tableLoaders;
|
|
||||||
private final List<FileAggregator> fileAggregators;
|
|
||||||
private final ExcelExporter excelExporter;
|
|
||||||
private final JsonExporter jsonExporter;
|
|
||||||
private final SettingsService settingsService;
|
|
||||||
private final TableMetaDataUtil tableMetaDataUtil;
|
|
||||||
|
|
||||||
public IndicSearchService(List<FirstLevelTableLoader> tableLoaders,
|
|
||||||
List<FileAggregator> fileAggregators,
|
|
||||||
ExcelExporter excelExporter,
|
|
||||||
JsonExporter jsonExporter,
|
|
||||||
SettingsService settingsService,
|
|
||||||
TableMetaDataUtil tableMetaDataUtil) {
|
|
||||||
this.tableLoaders = tableLoaders;
|
|
||||||
this.fileAggregators = fileAggregators;
|
|
||||||
this.excelExporter = excelExporter;
|
|
||||||
this.jsonExporter = jsonExporter;
|
|
||||||
this.settingsService = settingsService;
|
|
||||||
this.tableMetaDataUtil = tableMetaDataUtil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddSearchJobResponseDto addSearchJob(AddSearchJobRequestDto request) {
|
public AddSearchJobResponseDto addSearchJob(AddSearchJobRequestDto request) {
|
||||||
UUID taskId = UUID.randomUUID();
|
UUID jobId = indicSearchJobManager.addSearchJob(request);
|
||||||
|
|
||||||
StopWatch sw = new StopWatch();
|
return new AddSearchJobResponseDto(jobId);
|
||||||
|
|
||||||
// Подготовительный этап. Получение мета данных из БД
|
|
||||||
log.info("Prep step started. Getting metadata from database");
|
|
||||||
sw.start();
|
|
||||||
|
|
||||||
Map<String, TableMetaData> tableMetaDataMap = tableMetaDataUtil.prepareTableMetaData(request.getDataType(), request.getSearchParameter());
|
|
||||||
long rowLimit = Long.parseLong(settingsService.getSettingsValueByName("LimitDB"));
|
|
||||||
|
|
||||||
sw.stop();
|
|
||||||
log.info(sw.prettyPrint());
|
|
||||||
sw = new StopWatch();
|
|
||||||
|
|
||||||
// Первый этап. Получение данных из БД и заполнение файлов
|
|
||||||
log.info("First step started. Getting data from database and filling csv files");
|
|
||||||
sw.start();
|
|
||||||
|
|
||||||
Map<String, Path> tableFileMap = new ConcurrentHashMap<>();
|
|
||||||
tableLoaders.parallelStream().forEach(tl -> tl.searchBy(
|
|
||||||
taskId,
|
|
||||||
request.getDataType(),
|
|
||||||
request.getSearchParameter(),
|
|
||||||
request.getSearchValue(),
|
|
||||||
request.getMemberCode().substring(0, 6),
|
|
||||||
tableFileMap,
|
|
||||||
tableMetaDataMap
|
|
||||||
));
|
|
||||||
|
|
||||||
sw.stop();
|
|
||||||
log.info(sw.prettyPrint());
|
|
||||||
sw = new StopWatch();
|
|
||||||
|
|
||||||
// Второй этап. Объединение файлов в один
|
|
||||||
log.info("Second step started. Aggregating table files into one.");
|
|
||||||
sw.start();
|
|
||||||
|
|
||||||
MutableLong rowCounter = new MutableLong();
|
|
||||||
Path aggregatedFile = fileAggregators.stream().map(fa -> fa.aggregateFiles(
|
|
||||||
taskId,
|
|
||||||
request.getDataType(),
|
|
||||||
request.getSearchParameter(),
|
|
||||||
request.getSearchValue(),
|
|
||||||
request.getMemberCode().substring(0, 6),
|
|
||||||
tableFileMap,
|
|
||||||
rowCounter))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
sw.stop();
|
|
||||||
log.info(sw.prettyPrint());
|
|
||||||
sw = new StopWatch();
|
|
||||||
|
|
||||||
for (Path tableFilePath : tableFileMap.values()) {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(tableFilePath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalException("Failed to delete file " + tableFilePath + " " + e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Третий этап. Экспорт агрегированного файла в JSON или XLSX
|
public JobStatusResponseDto getJobStatus(UUID id) {
|
||||||
log.info("Third step started. Exporting aggregated file into JSON/XLSX.");
|
JobStatus jobStatus = indicSearchJobManager.getJobStatus(id);
|
||||||
sw.start();
|
|
||||||
|
|
||||||
if (rowCounter.longValue() < rowLimit && !IS_EXPORT_AS_EXCEL) {
|
return new JobStatusResponseDto(jobStatus);
|
||||||
jsonExporter.writeJsonFile(aggregatedFile, taskId, request.getSearchParameter(), tableMetaDataMap);
|
|
||||||
} else {
|
|
||||||
tableMetaDataUtil.fillTableMetaDataWithLocalization(tableMetaDataMap);
|
|
||||||
excelExporter.writeExcelFile(aggregatedFile, taskId, rowCounter.longValue(), tableMetaDataMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.stop();
|
public ResponseEntity<?> export(UUID id, ExportType exportType) {
|
||||||
log.info(sw.prettyPrint());
|
return indicSearchJobManager.export(id, exportType);
|
||||||
|
|
||||||
try {
|
|
||||||
if (aggregatedFile != null) {
|
|
||||||
Files.deleteIfExists(aggregatedFile);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalException("Failed to delete file " + aggregatedFile + " " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JobStatusResponseDto getJobStatus(Long id) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<?> export(Long id, String exportType) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class IndicSearchServiceApplicationTests {
|
class IndicSearchProcessorApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
Loading…
Add table
Reference in a new issue