new exception

This commit is contained in:
etreschenkov 2025-07-01 15:47:45 +03:00 committed by Mikhail Trofimov
parent 307f43f49b
commit 392b99aca4
13 changed files with 325 additions and 66 deletions

View file

@ -1,5 +1,6 @@
package ru.nbch.credit_tracker.config.rest; package ru.nbch.credit_tracker.config.rest;
import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -7,13 +8,19 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.nbch.credit_tracker.entities.response.errors.CommonError; import ru.nbch.credit_tracker.entities.response.errors.CommonError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringActivePackagesError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringDownloadError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringPackageDeleteError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringReportListError;
import ru.nbch.credit_tracker.exceptions.SignalClientException;
import ru.nbch.credit_tracker.exceptions.SignalErrorException; import ru.nbch.credit_tracker.exceptions.SignalErrorException;
import ru.nbch.credit_tracker.utils.error.SimpleMessageResolver;
import java.nio.charset.Charset;
@RestControllerAdvice @RestControllerAdvice
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
private final SimpleMessageResolver messageResolver = new SimpleMessageResolver();
@ExceptionHandler(value = SignalErrorException.class, produces = MediaType.APPLICATION_XML_VALUE) @ExceptionHandler(value = SignalErrorException.class, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<CommonError> handleInvalidUser(SignalErrorException ex) { public ResponseEntity<CommonError> handleInvalidUser(SignalErrorException ex) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
@ -24,4 +31,29 @@ public class GlobalExceptionHandler {
return new ResponseEntity<>(ex.getError(), headers, HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(ex.getError(), headers, HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
@ExceptionHandler(value = SignalClientException.class, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<CommonError> handleClientException(SignalClientException ex) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "xml", Charset.forName("windows-1251")));
CommonError commonError = getCommonError(ex);
commonError.setStatus("error");
commonError.setErrorCode(ex.getErrorMessage().getSubject().getErrorCode());
commonError.setErrorText(messageResolver.resolve(ex.getErrorMessage()));
return new ResponseEntity<>(commonError, headers, HttpStatus.BAD_REQUEST);
}
private static CommonError getCommonError(SignalClientException ex) {
CommonError commonError;
switch (ex.getErrorFormat()) {
case MonitoringError -> commonError = new MonitoringError();
case MonitoringActivePackagesError -> commonError = new MonitoringActivePackagesError();
case MonitoringDownloadError -> commonError = new MonitoringDownloadError();
case MonitoringReportListError -> commonError = new MonitoringReportListError();
case MonitoringPackageDeleteError -> commonError = new MonitoringPackageDeleteError();
default -> throw new IllegalStateException("Unsupported error format: " + ex.getErrorFormat());
}
return commonError;
}
} }

View file

@ -1,6 +1,7 @@
package ru.nbch.credit_tracker.entities.dto; package ru.nbch.credit_tracker.entities.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@ -11,6 +12,7 @@ import java.time.LocalDateTime;
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Builder
public class SubjectData { public class SubjectData {
private Integer id; private Integer id;
private Integer packageId; private Integer packageId;

View file

@ -0,0 +1,41 @@
package ru.nbch.credit_tracker.enums;
import java.util.Arrays;
import java.util.Collections;
public class EnumKeyMessage {
private final IEnumKeyCode subject;
/**
* Параметры для подстановки в текст сообщения. Все Serializable.
* args not null.
*/
private Object[] args;
public EnumKeyMessage(IEnumKeyCode subject) {
this.subject = subject;
this.args = Collections.emptyList().toArray(new Object[0]);
}
public EnumKeyMessage(IEnumKeyCode subject, Object... args) {
this.subject = subject;
this.args = args;
}
public IEnumKeyCode getSubject() {
return subject;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
@Override
public String toString() {
return "EnumKeyMessage{subject=" + subject + ", args: " + Arrays.toString(args) + "}";
}
}

View file

@ -0,0 +1,102 @@
package ru.nbch.credit_tracker.enums;
import java.io.Serializable;
import java.util.Objects;
public interface IEnumKeyCode extends Serializable {
String getKey();
String getCode();
default boolean equalsByKey(String key) {
return key != null && getKey().equalsIgnoreCase(key);
}
/**
* Проверяет что среди данного набора Enum, присутствует элемент с данным id
* id может быть null
*/
static <T extends Enum<T> & IEnumKeyCode> boolean contains(String id, T... enumSet) {
for (T e : enumSet) {
if (Objects.equals(id, e.getKey()))
return true;
}
return false;
}
static <T extends Enum<T> & IEnumKeyCode> boolean containsIgnoreCase(String id, T... enumSet) {
for (T e : enumSet) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(id)) {
return true;
}
}
return false;
}
static <T extends Enum<T> & IEnumKeyCode> boolean contains(T e, T... enumSet) {
for (T enumEl : enumSet) {
if (enumEl.equals(e))
return true;
}
return false;
}
/**
* Проверяет что среди всех Enum данного класса, присутствует элемент с данным id
* id может быть null
*/
static <T extends Enum<T> & IEnumKeyCode> boolean contains(String key, Class<T> enumClass) {
for (T e : enumClass.getEnumConstants()) {
if (Objects.equals(key, e.getKey()))
return true;
}
return false;
}
/**
* Возвращает Enum по id, если в заданном классе такой определен
* id может быть null
*
* @return Enum если нашел, иначе <tt>null</tt>
*/
static <T extends Enum<T> & IEnumKeyCode> T getEnumByKey(Class<T> enumClass, String key) {
for (T e : enumClass.getEnumConstants()) {
if (Objects.equals(key, e.getKey()))
return e;
}
return null;
}
static <T extends Enum<T> & IEnumKeyCode> T getEnumByKeyOrUndefined(Class<T> enumClass, String key) {
T enumByKey = getEnumByKey(enumClass, key);
return enumByKey == null ? getUndefined(enumClass) : enumByKey;
}
static <T extends Enum<T> & IEnumKeyCode> T getUndefined(Class<T> enumClass) {
T enumByKey = getEnumByKey(enumClass, UNDEFINED_VALUE);
if (enumByKey == null) {
throw new IllegalArgumentException("Undefined value not found in enum " + enumClass);
}
return enumByKey;
}
default boolean theSameAs(Object other) {
if (!(other instanceof IEnumKeyCode otherCasted)) {
return false;
}
return this.getKey().equalsIgnoreCase(otherCasted.getKey());
}
default String getErrorCode() {
String key = getKey();
String[] split = key.split("\\.");
if (split.length < 1) return null;
String s = split[split.length - 1];
if (!s.matches("\\d+")) return null;
return s;
}
static String getKeyOrNull(IEnumKeyCode enumVal) {
return enumVal == null ? null : enumVal.getKey();
}
String UNDEFINED_VALUE = "_Undefined";
}

View file

@ -1,9 +1,6 @@
package ru.nbch.credit_tracker.enums; package ru.nbch.credit_tracker.enums;
import lombok.Getter; public enum SignalError implements IEnumKeyCode {
@Getter
public enum SignalError {
ERROR_001("001", "Ошибка распаковки архива"), ERROR_001("001", "Ошибка распаковки архива"),
ERROR_002("002", "Отсутствует обязательный элемент: "), ERROR_002("002", "Отсутствует обязательный элемент: "),
ERROR_003("003", "Элемент ClientId не соответствует формату XXXXXXXXX"), ERROR_003("003", "Элемент ClientId не соответствует формату XXXXXXXXX"),
@ -31,4 +28,16 @@ public enum SignalError {
this.code = code; this.code = code;
this.message = message; this.message = message;
} }
@Override
public String getKey() {
return message;
}
@Override
public String getCode() {
return code;
}
} }

View file

@ -0,0 +1,10 @@
package ru.nbch.credit_tracker.enums;
public enum XmlErrorFormat {
MonitoringActivePackagesError,
MonitoringDownloadError,
MonitoringError,
MonitoringPackageDeleteError,
MonitoringReportListError
}

View file

@ -0,0 +1,18 @@
package ru.nbch.credit_tracker.exceptions;
import lombok.Getter;
import ru.nbch.credit_tracker.enums.EnumKeyMessage;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
@Getter
public class SignalClientException extends RuntimeException {
private final EnumKeyMessage errorMessage;
private final XmlErrorFormat errorFormat;
public SignalClientException(EnumKeyMessage errorMessage,
XmlErrorFormat errorFormat) {
this.errorMessage = errorMessage;
this.errorFormat = errorFormat;
}
}

View file

@ -19,9 +19,9 @@ public class SignalErrorException extends RuntimeException {
errorMessage.setStatus("error"); errorMessage.setStatus("error");
errorMessage.setErrorCode(error.getCode()); errorMessage.setErrorCode(error.getCode());
if (tag != null) { if (tag != null) {
errorMessage.setErrorText(error.getMessage() + " " + tag); errorMessage.setErrorText(error.getKey() + " " + tag);
} else { } else {
errorMessage.setErrorText(error.getMessage()); errorMessage.setErrorText(error.getKey());
} }
return errorMessage; return errorMessage;
} }

View file

@ -1,5 +1,14 @@
package ru.nbch.credit_tracker.facade; package ru.nbch.credit_tracker.facade;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.FileUrlResource; import org.springframework.core.io.FileUrlResource;
@ -12,13 +21,19 @@ import ru.nbch.credit_tracker.entities.request.active_packages.MonitoringActiveP
import ru.nbch.credit_tracker.entities.request.monitoring_request.MonitoringRequest; import ru.nbch.credit_tracker.entities.request.monitoring_request.MonitoringRequest;
import ru.nbch.credit_tracker.entities.request.monitoring_request.Subject; import ru.nbch.credit_tracker.entities.request.monitoring_request.Subject;
import ru.nbch.credit_tracker.entities.response.active_packages.MonitoringActivePackagesResponse; import ru.nbch.credit_tracker.entities.response.active_packages.MonitoringActivePackagesResponse;
import ru.nbch.credit_tracker.entities.response.errors.*; import ru.nbch.credit_tracker.entities.response.errors.MonitoringActivePackagesError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringDownloadError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringPackageDeleteError;
import ru.nbch.credit_tracker.entities.response.errors.MonitoringReportListError;
import ru.nbch.credit_tracker.entities.response.success.MonitoringPackageDeleteResponse; import ru.nbch.credit_tracker.entities.response.success.MonitoringPackageDeleteResponse;
import ru.nbch.credit_tracker.entities.response.success.MonitoringReportListResponse; import ru.nbch.credit_tracker.entities.response.success.MonitoringReportListResponse;
import ru.nbch.credit_tracker.entities.response.success.MonitoringResponse; import ru.nbch.credit_tracker.entities.response.success.MonitoringResponse;
import ru.nbch.credit_tracker.entities.signal_package.Signals; import ru.nbch.credit_tracker.entities.signal_package.Signals;
import ru.nbch.credit_tracker.enums.SignalError; import ru.nbch.credit_tracker.enums.SignalError;
import ru.nbch.credit_tracker.enums.Status; 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.exceptions.SignalErrorException; import ru.nbch.credit_tracker.exceptions.SignalErrorException;
import ru.nbch.credit_tracker.service.SignalService; import ru.nbch.credit_tracker.service.SignalService;
import ru.nbch.credit_tracker.service.indic.PhoneService; import ru.nbch.credit_tracker.service.indic.PhoneService;
@ -28,14 +43,8 @@ import ru.nbch.credit_tracker.service.xml.jaxb.IXmlProcessor;
import ru.nbch.credit_tracker.service.xml.jaxb.XmlUtilService; import ru.nbch.credit_tracker.service.xml.jaxb.XmlUtilService;
import ru.nbch.credit_tracker.utils.CTUtil; import ru.nbch.credit_tracker.utils.CTUtil;
import ru.nbch.credit_tracker.utils.ValidationUtil; import ru.nbch.credit_tracker.utils.ValidationUtil;
import ru.nbch.credit_tracker.utils.ZipUtils;
import java.io.File; import static ru.nbch.credit_tracker.utils.error.ExceptionUtils.excp;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@Slf4j @Slf4j
@Service @Service
@ -76,63 +85,43 @@ public class SignalFacade {
} }
// Валидация Архива на количетсво файлов, размер и расширение // Валидация Архива на количетсво файлов, размер и расширение
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-файл
throw new SignalErrorException(SignalError.ERROR_001, null, new MonitoringError(), true);
}
if (!entry.getName().toLowerCase().endsWith(".xml")) { // Файл должен быть с расширением xml
throw new SignalErrorException(SignalError.ERROR_001, null, new MonitoringError(), true);
}
xmlEntry = entry;
} else {
throw new SignalErrorException(SignalError.ERROR_001, null, new MonitoringError(), true); // Архив должен содержать ровно один XML-файл. Никаких доп директорий
}
zipInputStream.closeEntry();
entry = zipInputStream.getNextEntry();
}
if (fileCount == 0) { // Архив должен содержать ровно один XML-файл
throw new SignalErrorException(SignalError.ERROR_001, null, new MonitoringError(), true);
}
if (xmlEntry.getSize() > MAX_XML_FILE_SIZE) { // максимально 2 Гб до запаковки
throw new SignalErrorException(SignalError.ERROR_009, null, new MonitoringError(), true);
}
} catch (Exception e) {
if (e instanceof SignalErrorException) {
throw (SignalErrorException) e;
}
log.error(e.getMessage(), e);
throw new SignalErrorException(SignalError.ERROR_100, null, new MonitoringError(), false);
}
// Теперь знаем, что в архиве лежит ровно один файл с раширением xml и он весит меньше 2гб. Можно валидировать сам xml
// Пробуем распарсить xml
MonitoringRequest monitoringRequest; MonitoringRequest monitoringRequest;
Integer packageId; Integer packageId;
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) { ZipInputStream zipInputStream = null;
try {
zipInputStream = new ZipInputStream(file.getInputStream());
Optional<ZipEntry> xmlFileEntry = ZipUtils.extractValidEntry(zipInputStream);
if (xmlFileEntry.isEmpty()) {
throw excp(SignalError.ERROR_001, XmlErrorFormat.MonitoringError);
}
if (xmlFileEntry.get().getSize() > MAX_XML_FILE_SIZE) {
throw excp(SignalError.ERROR_009, XmlErrorFormat.MonitoringError);
}
zipInputStream.close();
zipInputStream = new ZipInputStream(file.getInputStream());
zipInputStream.getNextEntry(); zipInputStream.getNextEntry();
IXmlProcessor processor = xmlUtilService.createJaxbProcessor(); IXmlProcessor processor = xmlUtilService.createJaxbProcessor();
monitoringRequest = processor.read(zipInputStream, MonitoringRequest.class); monitoringRequest = processor.read(zipInputStream, MonitoringRequest.class);
} catch (Exception e) { } catch (Exception e) {
if (e instanceof SignalErrorException) { if (e instanceof SignalClientException) {
throw (SignalErrorException) e; throw (SignalClientException) e;
} }
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
throw new SignalErrorException(SignalError.ERROR_100, null, new MonitoringError(), false); //todo throw new SignalServerException(e);
throw excp(SignalError.ERROR_100, XmlErrorFormat.MonitoringError);
} finally {
if (zipInputStream != null) {
try {
zipInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
//todo throw new SignalServerException(e);
} }
}
}
// валидация xml // валидация xml
try { try {

View file

@ -35,6 +35,7 @@ import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
@Service @Service
public class XmlUtilService { public class XmlUtilService {
//сделать бином
public IXmlProcessor createJaxbProcessor() throws JAXBException { public IXmlProcessor createJaxbProcessor() throws JAXBException {
return new XmlProcessorImplAnyClasses(Charset.forName("windows-1251"), return new XmlProcessorImplAnyClasses(Charset.forName("windows-1251"),
// unmarshalling classes // unmarshalling classes

View file

@ -0,0 +1,28 @@
package ru.nbch.credit_tracker.utils;
import java.io.IOException;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ZipUtils {
public static Optional<ZipEntry> extractValidEntry(ZipInputStream inputStream) throws IOException {
Optional<ZipEntry> zipEntry = Optional.empty();
ZipEntry entry;
while ((entry = inputStream.getNextEntry()) != null) {
if (!entry.isDirectory()) {
if (zipEntry.isPresent()) {
//если мы уже нашли валидный xml файл, то обработка всего файла недоступна, возвращаем пустой entry
return Optional.empty();
}
if (entry.getName().toLowerCase().endsWith(".xml")) { // Файл должен быть с расширением xml
zipEntry = Optional.of(entry);
}
}
inputStream.closeEntry();
}
return zipEntry;
}
}

View file

@ -0,0 +1,15 @@
package ru.nbch.credit_tracker.utils.error;
import ru.nbch.credit_tracker.enums.EnumKeyMessage;
import ru.nbch.credit_tracker.enums.IEnumKeyCode;
import ru.nbch.credit_tracker.enums.XmlErrorFormat;
import ru.nbch.credit_tracker.exceptions.SignalClientException;
public class ExceptionUtils {
// public static SignalClientException excp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat) {
// return new SignalClientException(new EnumKeyMessage(enumKeyCode), xmlErrorFormat);
// }
public static SignalClientException excp(IEnumKeyCode enumKeyCode, XmlErrorFormat xmlErrorFormat, Object... args) {
return new SignalClientException(new EnumKeyMessage(enumKeyCode, args), xmlErrorFormat);
}
}

View file

@ -0,0 +1,12 @@
package ru.nbch.credit_tracker.utils.error;
import java.util.Arrays;
import ru.nbch.credit_tracker.enums.EnumKeyMessage;
public class SimpleMessageResolver {
public String resolve(EnumKeyMessage errorMessage) {
if (errorMessage == null) return "null";
return String.format("(%s) args %s", errorMessage.getSubject().getKey(), Arrays.toString(errorMessage.getArgs()));
}
}