From 86b832d03c4b628bed744d25c373f7f0a3cfad6e Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Wed, 21 May 2025 19:01:59 +0300 Subject: [PATCH] http://jira.mfd.msk:8088/browse/CLS-843 --- .../BankAccountValidationConfig.java | 85 ++++++++++-- .../config/validation/ValidationConfig.java | 8 +- .../clearing/account/errors/AccountError.java | 1 + .../service/BankAccountServiceTest.java | 45 ++++-- .../src/main/resources/meta/dictionaries.xml | 1 + .../common/rules/ValidateSymbolsRule.java | 129 ++++++++++++++++++ 6 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/ValidateSymbolsRule.java diff --git a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java index 2fbb58b85..4b8856552 100644 --- a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java +++ b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/BankAccountValidationConfig.java @@ -1,5 +1,8 @@ package ru.spcex.clearing.account.config.validation; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import ru.clearing.classes.statics.data.account.Account; @@ -13,10 +16,9 @@ import ru.spcex.clearing.platform.messaging.domain.cud.account.BankAccountNewReq import ru.spcex.clearing.platform.messaging.domain.cud.account.BankAccountUpdateRequest; import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonIdRequest; import ru.spcex.clearing.validation.common.rules.DictionaryPresentRule; -import ru.spcex.clearing.validation.common.rules.FieldNotBlankRequiredRule; import ru.spcex.clearing.validation.common.rules.FieldRequiredRule; import ru.spcex.clearing.validation.common.rules.IdPresentRule; -import ru.spcex.clearing.validation.common.rules.specific.FieldRequiredSpecificRule; +import ru.spcex.clearing.validation.common.rules.ValidateSymbolsRule; import ru.spcex.clearing.validation.common.rules.specific.IdPresentSpecificRule; import ru.spcex.platform.classes.base.SpcexObjectBase; import ru.spcex.platform.enumeration.AccountStatus; @@ -27,11 +29,6 @@ import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.validation.IValidator; import ru.spcex.platform.utils.validation.ValidatorImpl; -import java.util.Collection; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - @Configuration public class BankAccountValidationConfig { @@ -48,6 +45,7 @@ public class BankAccountValidationConfig { addImdg.accept(IMDGDistributedNames.Map_Company); addImdg.accept(IMDGDistributedNames.Map_ServiceStatusDictionary); addImdg.accept(IMDGDistributedNames.Map_CurrencyCodeDictionary); + addImdg.accept(IMDGDistributedNames.Map_ValidationSymbols); return new ValidatorImpl<>(context, IdPresentRule.instance("companyId", BankAccountNewRequest::getCompanyId, @@ -63,9 +61,6 @@ public class BankAccountValidationConfig { CurrencyCodeDictionary.class, AccountError.RequiredFieldEmpty, AccountError.DictionaryNotFound), - FieldRequiredRule.instance("bankIdentificationCode", - BankAccountNewRequest::getBankIdentificationCode, - AccountError.RequiredFieldEmpty), FieldRequiredRule.instance("bankName", BankAccountNewRequest::getBankName, AccountError.RequiredFieldEmpty), @@ -75,8 +70,41 @@ public class BankAccountValidationConfig { ServiceStatusDictionary.class, AccountError.RequiredFieldEmpty, AccountError.DictionaryNotFound, - false) - ); + false), + ValidateSymbolsRule.required("bankIdentificationCode", + BankAccountNewRequest::getBankIdentificationCode, + AccountError.RequiredFieldEmpty, + AccountError.BadSymbol), + ValidateSymbolsRule.required("bankName", + BankAccountNewRequest::getBankName, + AccountError.RequiredFieldEmpty, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("correspondentAccount", + BankAccountNewRequest::getCorrespondentAccount, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("correspondentAccountName", + BankAccountNewRequest::getCorrespondentAccountName, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("destination", + BankAccountNewRequest::getDestination, + AccountError.BadSymbol), + ValidateSymbolsRule.required("account", + BankAccountNewRequest::getAccount, + AccountError.RequiredFieldEmpty, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("taxpayerIdentificationNumber", + BankAccountNewRequest::getTaxpayerIdentificationNumber, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("taxRegistrationReasonCode", + BankAccountNewRequest::getTaxRegistrationReasonCode, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("swiftCode", + BankAccountNewRequest::getSwiftCode, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("intermediarySwiftCode", + BankAccountNewRequest::getIntermediarySwiftCode, + AccountError.BadSymbol) + ); }; } @@ -92,6 +120,7 @@ public class BankAccountValidationConfig { addImdg.accept(IMDGDistributedNames.Map_CurrencyCodeDictionary); addImdg.accept(IMDGDistributedNames.Map_BankAccount); addImdg.accept(IMDGDistributedNames.Map_Company); + addImdg.accept(IMDGDistributedNames.Map_ValidationSymbols); return new ValidatorImpl<>(context, IdPresentRule.instance("id", BankAccountUpdateRequest::getId, @@ -115,7 +144,37 @@ public class BankAccountValidationConfig { CurrencyCodeDictionary.class, AccountError.RequiredFieldEmpty, AccountError.DictionaryNotFound, - false) + false), + ValidateSymbolsRule.notRequired("bankIdentificationCode", + BankAccountUpdateRequest::getBankIdentificationCode, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("bankName", + BankAccountUpdateRequest::getBankName, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("correspondentAccount", + BankAccountUpdateRequest::getCorrespondentAccount, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("correspondentAccountName", + BankAccountUpdateRequest::getCorrespondentAccountName, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("destination", + BankAccountUpdateRequest::getDestination, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("account", + BankAccountUpdateRequest::getAccount, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("taxpayerIdentificationNumber", + BankAccountUpdateRequest::getTaxpayerIdentificationNumber, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("taxRegistrationReasonCode", + BankAccountUpdateRequest::getTaxRegistrationReasonCode, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("swiftCode", + BankAccountUpdateRequest::getSwiftCode, + AccountError.BadSymbol), + ValidateSymbolsRule.notRequired("intermediarySwiftCode", + BankAccountUpdateRequest::getIntermediarySwiftCode, + AccountError.BadSymbol) ); }; } diff --git a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/ValidationConfig.java b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/ValidationConfig.java index e72ce5699..0df9d6387 100644 --- a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/ValidationConfig.java +++ b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/config/validation/ValidationConfig.java @@ -17,9 +17,14 @@ import ru.clearing.classes.statics.data.company.ClearingMemberCategory; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.company.CompanySymbols; import ru.clearing.classes.statics.data.misc.Currency; +import ru.clearing.classes.statics.data.misc.ValidationSymbols; import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; import ru.clearing.classes.statics.data.registry.TradingClearingRegistryList; -import ru.clearing.platform.dictionary.*; +import ru.clearing.platform.dictionary.AccountTypeDictionary; +import ru.clearing.platform.dictionary.ClearingAccountTypeDictionary; +import ru.clearing.platform.dictionary.CurrencyCodeDictionary; +import ru.clearing.platform.dictionary.ServiceStatusDictionary; +import ru.clearing.platform.dictionary.TradingClearingRegistryTypeDictionary; import ru.spcex.clearing.imdg.IMDGDistributedNames; import ru.spcex.clearing.validation.common.ValidationHelper; import ru.spcex.platform.classes.base.SpcexObjectBase; @@ -52,6 +57,7 @@ public class ValidationConfig { addImdg.accept(IMDGDistributedNames.Map_TradingClearingRegistryList, TradingClearingRegistryList.class); addImdg.accept(IMDGDistributedNames.Map_Currency, Currency.class); addImdg.accept(IMDGDistributedNames.Map_TradingClearingRegistryTypeDictionary, TradingClearingRegistryTypeDictionary.class); + addImdg.accept(IMDGDistributedNames.Map_ValidationSymbols, ValidationSymbols.class); //for ClientCodeValidationConfig addImdg.accept(IMDGDistributedNames.Map_ClientCode, ClientCode.class); diff --git a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/errors/AccountError.java b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/errors/AccountError.java index 23f3cfd0d..1114b546d 100644 --- a/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/errors/AccountError.java +++ b/clearing-parent/account-service/src/main/java/ru/spcex/clearing/account/errors/AccountError.java @@ -30,6 +30,7 @@ public enum AccountError implements IErrorEnumId { ACCOUNT_NOT_FOUND_GTW(5029L), COMPANY_NOT_FOUND_GTW(5030L), CurrencyForTradingClearingRegistryListAlreadyUsed(5031L), + BadSymbol(5032L), TradingClearingRegistryNotFound(3022L), CurrencyNotFound(1016L), ; diff --git a/clearing-parent/account-service/src/test/java/ru/spcex/clearing/account/service/BankAccountServiceTest.java b/clearing-parent/account-service/src/test/java/ru/spcex/clearing/account/service/BankAccountServiceTest.java index a19dd8b49..7e7122587 100644 --- a/clearing-parent/account-service/src/test/java/ru/spcex/clearing/account/service/BankAccountServiceTest.java +++ b/clearing-parent/account-service/src/test/java/ru/spcex/clearing/account/service/BankAccountServiceTest.java @@ -1,11 +1,16 @@ package ru.spcex.clearing.account.service; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.PostConstruct; import org.apache.kafka.clients.consumer.MockConsumer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; @@ -15,6 +20,7 @@ import ru.clearing.classes.statics.data.account.BankAccount; import ru.clearing.classes.statics.data.company.ClearingMemberCategory; import ru.clearing.classes.statics.data.company.Company; import ru.clearing.classes.statics.data.company.relation.Relation; +import ru.clearing.classes.statics.data.misc.ValidationSymbols; import ru.clearing.platform.dictionary.CurrencyCodeDictionary; import ru.spcex.clearing.account.config.BeanConfiguration; import ru.spcex.clearing.account.config.validation.AccountValidationConfig; @@ -29,27 +35,25 @@ import ru.spcex.clearing.platform.messaging.domain.cud.account.BankAccountNewReq import ru.spcex.clearing.platform.messaging.domain.cud.account.BankAccountUpdateRequest; import ru.spcex.clearing.platform.messaging.domain.cud.common.CommonDeleteRequest; import ru.spcex.clearing.platform.messaging.service.RequestInfoUpdate; +import static ru.spcex.clearing.platform.messaging.service.Status.Error; import ru.spcex.clearing.test.MatcherFactory.Matcher; +import static ru.spcex.clearing.test.MatcherFactory.usingIgnoringFieldsComparator; import ru.spcex.clearing.test.TestObjectCreator; +import static ru.spcex.clearing.test.TestUtils.*; import ru.spcex.clearing.test.config.ImdgTestConfig; import ru.spcex.clearing.test.config.KafkaTestConfig; -import ru.spcex.platform.enumeration.*; +import static ru.spcex.clearing.test.config.KafkaTestConfig.getCaptor; +import ru.spcex.platform.enumeration.AccountType; +import ru.spcex.platform.enumeration.Allowed; +import ru.spcex.platform.enumeration.ClearingCategory; +import ru.spcex.platform.enumeration.Service; +import ru.spcex.platform.enumeration.Status; +import ru.spcex.platform.enumeration.WorkflowStatus; import ru.spcex.platform.imdg.api.Imdg; import ru.spcex.platform.imdg.api.ImdgProvider; import ru.spcex.platform.utils.enumeration.EnumMessage; import ru.spcex.platform.utils.enumeration.IMessageResolver; -import javax.annotation.PostConstruct; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; -import static ru.spcex.clearing.platform.messaging.service.Status.Error; -import static ru.spcex.clearing.test.MatcherFactory.usingIgnoringFieldsComparator; -import static ru.spcex.clearing.test.TestUtils.*; -import static ru.spcex.clearing.test.config.KafkaTestConfig.getCaptor; - @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { AccountHelper.class, @@ -89,6 +93,7 @@ public class BankAccountServiceTest { private Imdg currencyCodeDictionaryImdg; private Imdg clearingMemberCategoryImdg; private Imdg relationImdg; + private Imdg validationSymbolsImdg; @Autowired private IMessageResolver messageResolver; @@ -119,10 +124,26 @@ public class BankAccountServiceTest { relationImdg = hazelcastServiceTest.getImdg( IMDGDistributedNames.Map_Relation, Relation.class ); + validationSymbolsImdg = hazelcastServiceTest.getImdg( + IMDGDistributedNames.Map_ValidationSymbols, ValidationSymbols.class + ); CurrencyCodeDictionary currencyCodeDictionary = new CurrencyCodeDictionary(); currencyCodeDictionary.setCode("RUB"); currencyCodeDictionary.setName("RUB"); currencyCodeDictionaryImdg.insert(currencyCodeDictionary); + ValidationSymbols validationSymbols = new ValidationSymbols(); + validationSymbols.setId(1L); + validationSymbols.setSymbols("а-яА-ЯёЁ"); + validationSymbolsImdg.insert(validationSymbols); + validationSymbols.setId(2L); + validationSymbols.setSymbols("a-zA-Z"); + validationSymbolsImdg.insert(validationSymbols); + validationSymbols.setId(3L); + validationSymbols.setSymbols("0-9"); + validationSymbolsImdg.insert(validationSymbols); + validationSymbols.setId(4L); + validationSymbols.setSymbols("-_., ;\"%"); + validationSymbolsImdg.insert(validationSymbols); new TestObjectCreator(hazelcastServiceTest).createUserAdmin(1000L); } diff --git a/clearing-parent/backend-api/src/main/resources/meta/dictionaries.xml b/clearing-parent/backend-api/src/main/resources/meta/dictionaries.xml index 925e4c6d8..3300bd085 100644 --- a/clearing-parent/backend-api/src/main/resources/meta/dictionaries.xml +++ b/clearing-parent/backend-api/src/main/resources/meta/dictionaries.xml @@ -450,6 +450,7 @@ + diff --git a/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/ValidateSymbolsRule.java b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/ValidateSymbolsRule.java new file mode 100644 index 000000000..3de684663 --- /dev/null +++ b/clearing-parent/clearing-validation/src/main/java/ru/spcex/clearing/validation/common/rules/ValidateSymbolsRule.java @@ -0,0 +1,129 @@ +package ru.spcex.clearing.validation.common.rules; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.clearing.classes.statics.data.misc.ValidationSymbols; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.validation.ImdgValidationContext; +import ru.spcex.platform.utils.enumeration.EnumMessage; +import ru.spcex.platform.utils.enumeration.IErrorEnumId; +import ru.spcex.platform.utils.validation.IValidationRule; + +/** + * Проверка полей на символы из validation_symbols. + * Требует наличия в контексте мапы IMDGDistributedNames.Map_ValidationSymbols. + * + * @param - Класс проверяемого объекта + */ +public final class ValidateSymbolsRule implements IValidationRule> { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Pattern rangePattern = Pattern.compile("([\\p{L}0-9]-[\\p{L}0-9]|.)"); + + private final static ThreadLocal> allowedSymbols = new ThreadLocal<>(); + private final static ThreadLocal allowedSymbolsHash = new ThreadLocal<>(); + + private final String fieldName; + private final Function getter; + private final IErrorEnumId errorEmptyRequiredValue; + private final IErrorEnumId errorBadSymbol; + private final boolean required; + + public ValidateSymbolsRule( + String fieldName, + Function getter, + IErrorEnumId errorEmptyRequiredValue, + IErrorEnumId errorBadSymbol, + boolean required + ) { + this.fieldName = fieldName; + this.getter = getter; + this.errorEmptyRequiredValue = errorEmptyRequiredValue; + this.errorBadSymbol = errorBadSymbol; + this.required = required; + } + + public static ValidateSymbolsRule notRequired(String fieldName, + Function getter, + IErrorEnumId errorBadSymbol) { + return new ValidateSymbolsRule<>(fieldName, getter, null, errorBadSymbol, false); + } + + public static ValidateSymbolsRule required(String fieldName, + Function getter, + IErrorEnumId errorEmptyRequiredValue, + IErrorEnumId errorBadSymbol) { + return new ValidateSymbolsRule<>(fieldName, getter, errorEmptyRequiredValue, errorBadSymbol, true); + } + + @Override + public Optional validate(ImdgValidationContext context) { + String value = getter.apply(context.getValidatedObject()); + if (StringUtils.isBlank(value)) { + if (!required) return Optional.empty(); + else return Optional.of(new EnumMessage(errorEmptyRequiredValue, fieldName)); + } + + checkAndSetAllowed(context); + Set allowed = allowedSymbols.get(); + if (allowed.isEmpty()) { + log.warn("Can't validate field {}: empty validation_symbols, skipped", fieldName); + return Optional.empty(); + } + + Set invalidSymbols = new HashSet<>(); + for (int charIdx = 0; charIdx < value.length(); charIdx++) { + char character = value.charAt(charIdx); + if (!allowed.contains(character)) invalidSymbols.add(String.valueOf(character)); + } + + if (invalidSymbols.isEmpty()) return Optional.empty(); + + return Optional.of(new EnumMessage(errorBadSymbol, fieldName, String.join(", ", invalidSymbols))); + } + + /** + * Проверяет изменения в validation_symbols. Если изменения есть, создает новую коллекцию разрешенных символов + */ + private void checkAndSetAllowed(ImdgValidationContext context) { + Imdg validationSymbolsImdg = context.obtainMap( + IMDGDistributedNames.Map_ValidationSymbols, ValidationSymbols.class + ); + Collection validationSymbols = validationSymbolsImdg.getAllValues(); + String forHash = validationSymbols.stream().map(ValidationSymbols::getSymbols).collect(Collectors.joining()); + Integer hash = forHash.hashCode(); + if (!Objects.equals(allowedSymbolsHash.get(), hash)) { + Matcher matcher = rangePattern.matcher(forHash); + Set currAllowedSymbols = new HashSet<>(); + while (matcher.find()) { + String part = matcher.group(); + if (part.length() == 3 && part.charAt(1) == '-') { + char start = part.charAt(0); + char end = part.charAt(2); + if (start > end) { + log.warn("Invalid character range: {}, skipped", part); + continue; + } + for (char c = start; c <= end; c++) { + currAllowedSymbols.add(c); + } + } else { + currAllowedSymbols.add(part.charAt(0)); + } + } + allowedSymbols.set(currAllowedSymbols); + allowedSymbolsHash.set(hash); + } + } + +}