Aleksey Kulikov 2025-05-21 19:01:59 +03:00
parent 89fe02d557
commit 86b832d03c
6 changed files with 243 additions and 26 deletions

View file

@ -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,7 +70,40 @@ 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)
);
};
}

View file

@ -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);

View file

@ -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),
;

View file

@ -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<CurrencyCodeDictionary> currencyCodeDictionaryImdg;
private Imdg<ClearingMemberCategory> clearingMemberCategoryImdg;
private Imdg<Relation> relationImdg;
private Imdg<ValidationSymbols> 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);
}

View file

@ -450,6 +450,7 @@
<errorCode id="5029" code="ACNT" name="Счет %s не найден в клиринговой системе."/>
<errorCode id="5030" code="ACNT" name="Компания %s не найдена в клиринговой системе."/>
<errorCode id="5031" code="ACNT" name="Для данной валюты в ТКР уже задан счет."/>
<errorCode id="5032" code="ACNT" name="В поле %s недопустимый символ %s."/>
<!-- error code for balance-service -->
<errorCode id="5200" code="BLNC" name="Общая ошибка модуля balance-service."/>
<errorCode id="5210" code="BLNC" name="Клиринговая сессия неактивна."/>

View file

@ -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 <R> - Класс проверяемого объекта
*/
public final class ValidateSymbolsRule<R> implements IValidationRule<ImdgValidationContext<R>> {
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<Set<Character>> allowedSymbols = new ThreadLocal<>();
private final static ThreadLocal<Integer> allowedSymbolsHash = new ThreadLocal<>();
private final String fieldName;
private final Function<R, String> getter;
private final IErrorEnumId errorEmptyRequiredValue;
private final IErrorEnumId errorBadSymbol;
private final boolean required;
public ValidateSymbolsRule(
String fieldName,
Function<R, String> getter,
IErrorEnumId errorEmptyRequiredValue,
IErrorEnumId errorBadSymbol,
boolean required
) {
this.fieldName = fieldName;
this.getter = getter;
this.errorEmptyRequiredValue = errorEmptyRequiredValue;
this.errorBadSymbol = errorBadSymbol;
this.required = required;
}
public static <R> ValidateSymbolsRule<R> notRequired(String fieldName,
Function<R, String> getter,
IErrorEnumId errorBadSymbol) {
return new ValidateSymbolsRule<>(fieldName, getter, null, errorBadSymbol, false);
}
public static <R> ValidateSymbolsRule<R> required(String fieldName,
Function<R, String> getter,
IErrorEnumId errorEmptyRequiredValue,
IErrorEnumId errorBadSymbol) {
return new ValidateSymbolsRule<>(fieldName, getter, errorEmptyRequiredValue, errorBadSymbol, true);
}
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<R> 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<Character> allowed = allowedSymbols.get();
if (allowed.isEmpty()) {
log.warn("Can't validate field {}: empty validation_symbols, skipped", fieldName);
return Optional.empty();
}
Set<String> 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<R> context) {
Imdg<ValidationSymbols> validationSymbolsImdg = context.obtainMap(
IMDGDistributedNames.Map_ValidationSymbols, ValidationSymbols.class
);
Collection<ValidationSymbols> 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<Character> 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);
}
}
}