From c0f855c2a07b9df69d52e856f2d804a1fe365f50 Mon Sep 17 00:00:00 2001 From: Mikhail Trofimov Date: Tue, 3 Jun 2025 16:54:35 +0300 Subject: [PATCH] Added SignalController with setup monitoring method; Added check for validity of zip file and xml file, extentions and sizes; Added SignalError enum with error codes and messages; Added dummy FailureAnswerGenerator; --- .../controller/SignalController.java | 27 ++++++ .../credit_tracker/enums/SignalError.java | 26 +++++ .../credit_tracker/facade/SignalFacade.java | 96 +++++++++++++++++++ .../service/FailureAnswerGenerator.java | 13 +++ src/main/resources/application.properties | 7 +- 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/nbch/credit_tracker/controller/SignalController.java create mode 100644 src/main/java/ru/nbch/credit_tracker/enums/SignalError.java create mode 100644 src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java create mode 100644 src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java diff --git a/src/main/java/ru/nbch/credit_tracker/controller/SignalController.java b/src/main/java/ru/nbch/credit_tracker/controller/SignalController.java new file mode 100644 index 0000000..fca943f --- /dev/null +++ b/src/main/java/ru/nbch/credit_tracker/controller/SignalController.java @@ -0,0 +1,27 @@ +package ru.nbch.credit_tracker.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +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 ru.nbch.credit_tracker.facade.SignalFacade; + +@RestController +@RequestMapping("/api/v1/signal") +public class SignalController { + + private final SignalFacade signalFacade; + + public SignalController(SignalFacade signalFacade) { + this.signalFacade = signalFacade; + } + + @PostMapping("/set") + public ResponseEntity setupMonitoring(@RequestParam("file") MultipartFile file) { + return signalFacade.setupMonitoring(file); + } + +} + diff --git a/src/main/java/ru/nbch/credit_tracker/enums/SignalError.java b/src/main/java/ru/nbch/credit_tracker/enums/SignalError.java new file mode 100644 index 0000000..401e24f --- /dev/null +++ b/src/main/java/ru/nbch/credit_tracker/enums/SignalError.java @@ -0,0 +1,26 @@ +package ru.nbch.credit_tracker.enums; + +import lombok.Getter; + +@Getter +public enum SignalError { + ERROR_001("001", "Ошибка распаковки архива"), + ERROR_002("002", "Отсутствует обязательный элемент: "), + ERROR_003("003", "Элемент ClientId не соответствует формату XXXXXXXXX"), + ERROR_004("004", "Указан неуникальный Id в пакете: "), + ERROR_005("005", "Неверный логин или пароль"), + ERROR_006("006", "Доступ к услуге закрыт, обратитесь к курирующему менеджеру"), + ERROR_007("007", "В системе уже существует пакет с таким именем"), + ERROR_008("008", "PackageName не соответствует допустимому формату"), + ERROR_009("009", "Превышен допустимый размер пакета"), + ERROR_010("010", "Идентификатор субъекта (Id) не соответствует допустимому формату"), + ERROR_011("011", "Получен невалидный XML"); + + private final String code; + private final String message; + + SignalError(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java b/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java new file mode 100644 index 0000000..176b93a --- /dev/null +++ b/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java @@ -0,0 +1,96 @@ +package ru.nbch.credit_tracker.facade; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import ru.nbch.credit_tracker.enums.SignalError; +import ru.nbch.credit_tracker.service.FailureAnswerGenerator; + +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +@Service +public class SignalFacade { + private static final long MAX_XML_FILE_SIZE = 2L * 1024 * 1024 * 1024; + + private final FailureAnswerGenerator failureAnswerGenerator; + + public SignalFacade(FailureAnswerGenerator failureAnswerGenerator) { + this.failureAnswerGenerator = failureAnswerGenerator; + } + + public ResponseEntity setupMonitoring(MultipartFile file) { + if (file.isEmpty()) { + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + if (!isZipFile(file)) { + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) { + int fileCount = 0; + ZipEntry xmlEntry = null; + + ZipEntry entry = zipInputStream.getNextEntry(); + while (entry != null) { + if (!entry.isDirectory()) { + fileCount++; + if (fileCount > 1) { // Архив должен содержать ровно один XML-файл + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + if (!entry.getName().toLowerCase().endsWith(".xml")) { // Файл должен быть с расширением xml + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + xmlEntry = entry; + } else { + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); // Архив должен содержать ровно один XML-файл. Никаких доп директорий + } + zipInputStream.closeEntry(); + entry = zipInputStream.getNextEntry(); + } + + if (fileCount == 0) { // Архив должен содержать ровно один XML-файл + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + if (xmlEntry.getSize() > MAX_XML_FILE_SIZE) { // максимально 2 Гб до запаковки + return ResponseEntity.badRequest().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_009)); + } + + + AuthInfoData authInfoData = parseAuthInfo(zipInputStream); // TODO authorize + + + // TODO process xml file + + } catch (IOException e) { + return ResponseEntity.internalServerError().body(failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001)); + } + + return ResponseEntity.ok().body("OK"); // TODO generate success response + } + + + 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"); + } + + private AuthInfoData parseAuthInfo(ZipInputStream zipInputStream) throws IOException { + return new AuthInfoData(); + } + + @Getter + @Setter + private static class AuthInfoData { + private String username; + private String password; + } +} diff --git a/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java b/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java new file mode 100644 index 0000000..67c9643 --- /dev/null +++ b/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java @@ -0,0 +1,13 @@ +package ru.nbch.credit_tracker.service; + +import org.springframework.stereotype.Service; +import ru.nbch.credit_tracker.enums.SignalError; + +// TODO generate xml error file (mb return path ?) +@Service +public class FailureAnswerGenerator { + + public String generateFailureAnswer(SignalError error) { + return error.getCode() + ": " + error.getMessage(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 978350c..14d9b0d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,4 +17,9 @@ ct.signal-data-source-settings.password= ct.scoring-data-source-settings.driver=com.ibm.db2.jcc.DB2Driver ct.scoring-data-source-settings.url=jdbc:db2://10.230.227.101:2668/cprosd22:currentSchema=TEST_2_INDIC; ct.scoring-data-source-settings.username=tester -ct.scoring-data-source-settings.password=tester \ No newline at end of file +ct.scoring-data-source-settings.password=tester + + +# TODO Increase file size limit if needed +#spring.servlet.multipart.max-file-size=10MB +#spring.servlet.multipart.max-request-size=10MB \ No newline at end of file