ialbert 2022-09-22 19:51:37 +03:00
parent 0726c3e321
commit cef2444524
26 changed files with 613 additions and 8 deletions

View file

@ -36,6 +36,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>ru.spcex.platform</groupId>
<artifactId>platform-enum</artifactId>
</dependency>
</dependencies>
<build>

View file

@ -0,0 +1,51 @@
package ru.spcex.clearing.balance.config;
import ru.spcex.platform.classes.base.SpcexObjectBase;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.utils.validation.IValidatorContext;
import java.util.HashMap;
import java.util.Map;
public class ImdgValidationContext<T> implements IValidatorContext {
private final Map<String, Imdg<?>> imdgs;
private final Map<Enum<?>, Object> storedObjects;
private T validatedObject;
public ImdgValidationContext() {
this.imdgs = new HashMap<>();
this.storedObjects = new HashMap<>();
}
void addImdg(String key, Imdg<?> imdg) {
imdgs.put(key, imdg);
}
void setValidatedObject(T validatedObject) {
this.validatedObject = validatedObject;
}
public T getValidatedObject() {
return validatedObject;
}
public void storeObject(Enum<?> storedObject, Object anyObject) {
storedObjects.put(storedObject, anyObject);
}
@Override
@SuppressWarnings("unchecked")
public <S> S getStoredObject(Enum<?> storedObject) {
Object o = storedObjects.get(storedObject);
return (S) o;
}
@SuppressWarnings("unchecked")
public <V extends SpcexObjectBase> Imdg<V> obtainMap(String mapName, Class<V> clazz) {
Imdg<?> imdg = imdgs.get(mapName);
if (imdg == null) {
throw new IllegalStateException(mapName + " imdg wasn't added to validation context");
}
return (Imdg<V>) imdg;
}
}

View file

@ -0,0 +1,30 @@
package ru.spcex.clearing.balance.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import ru.spcex.platform.utils.enumeration.IMessageResolver;
import ru.spcex.platform.utils.enumeration.SpringPropertiesMessageResolver;
import java.util.Locale;
@Configuration
public class MessagesConfig {
@Bean("validation-error-messages")
public ResourceBundleMessageSource messages() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("messages/error");
source.setUseCodeAsDefaultMessage(true);
source.setDefaultEncoding("utf8");
source.setDefaultLocale(Locale.ROOT);
return source;
}
@Bean
public IMessageResolver errorResolver(@Qualifier("validation-error-messages") ResourceBundleMessageSource messageBundle) {
SpringPropertiesMessageResolver resolver = new SpringPropertiesMessageResolver(messageBundle);
resolver.setLocale("ru");
return resolver;
}
}

View file

@ -0,0 +1,38 @@
package ru.spcex.clearing.balance.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.sdf.SDf01;
import ru.spcex.clearing.balance.validation.Sdf01ValidationRule;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.platform.classes.base.SpcexObjectBase;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.utils.validation.IValidator;
import ru.spcex.platform.utils.validation.ValidatorImpl;
import java.util.function.BiConsumer;
import java.util.function.Function;
@Configuration
public class ValidationConfig {
@Autowired
@Bean("sdf01Validator")
public Function<SDf01, IValidator> sdf01Validator(ImdgProvider imdgProvider) {
return sDf01 -> {
ImdgValidationContext<SDf01> context = new ImdgValidationContext<>();
context.setValidatedObject(sDf01);
BiConsumer<String, Class<? extends SpcexObjectBase>> addImdg = (s, aClass) -> context.addImdg(s, imdgProvider.getImdg(s, aClass));
addImdg.accept(IMDGDistributedNames.Map_Account, Account.class);
return new ValidatorImpl<>(context,
Sdf01ValidationRule.AccountPresent,
Sdf01ValidationRule.CurrencyCode,
Sdf01ValidationRule.CurrentDateOnly,
Sdf01ValidationRule.Market,
Sdf01ValidationRule.accountType);
};
}
}

View file

@ -0,0 +1,23 @@
package ru.spcex.clearing.balance.errors;
import ru.spcex.platform.utils.enumeration.IEnumId;
public enum BalanceError implements IEnumId {
CompanyNotFound(5211L),
CurrencyNotFound(5213L),
CurrentDateOnly(5214L),
WrongMarket(5215L),
WrongAccount(5215L),
AccountNotPresent(-1L);
;
private final Long id;
BalanceError(Long id) {
this.id = id;
}
@Override
public Long getId() {
return id;
}
}

View file

@ -0,0 +1,28 @@
package ru.spcex.clearing.balance.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.enumeration.IMessageResolver;
@Component
public class LoggingService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final IMessageResolver errorResolver;
@Autowired
public LoggingService(@Qualifier("errorResolver") IMessageResolver errorResolver) {
this.errorResolver = errorResolver;
}
public void logError(String message, EnumMessage error, Object... args) {
log.error(message + " {}", args, errorResolver.resolve(error));
}
public void logError(EnumMessage error) {
log.error("{}", errorResolver.resolve(error));
}
}

View file

@ -0,0 +1,105 @@
package ru.spcex.clearing.balance.service;
import org.apache.kafka.clients.consumer.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.sdf.SDf01;
import ru.clearing.classes.statics.data.statement.Statement;
import ru.spcex.clearing.balance.errors.BalanceError;
import ru.spcex.clearing.balance.validation.ValidationStored;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.account.AccountNewRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.balance.StatementRequest;
import ru.spcex.clearing.platform.messaging.service.QueueConsumer;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
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.validation.IValidator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@Service
public class StatementService extends QueueConsumer implements InitializingBean {
private final Logger log = LoggerFactory.getLogger(getClass());
private final ImdgProvider imdgProvider;
private final KafkaSender kafkaReqProducer;
private final LoggingService errorLogger;
private final Imdg<SDf01> sdf01Imdg;
private final Imdg<Company> companyImdg;
private final Imdg<Account> accountImdg;
private final Imdg<Statement> statementImdg;
private final Function<SDf01, IValidator> sDf01Validator;
@Autowired
public StatementService(Consumer<String, Object> kafkaQueue, ImdgProvider imdgProvider, KafkaSender kafkaReqProducer, LoggingService errorLogger,
@Qualifier("sdf01Validator") Function<SDf01, IValidator> sDf01Validator) {
super(kafkaQueue);
this.imdgProvider = imdgProvider;
this.sdf01Imdg = imdgProvider.getImdg(IMDGDistributedNames.Map_SDf01, SDf01.class);
this.companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class);
this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class);
this.statementImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Statement, Statement.class);
this.kafkaReqProducer = kafkaReqProducer;
this.errorLogger = errorLogger;
this.sDf01Validator = sDf01Validator;
}
@Override
public void afterPropertiesSet() throws Exception {
callback(StatementRequest.class)
.setConsumer(this::process)
.forDestination(Consts.DESTINATION_SDF02_NEW, callbacks::put);
init();
}
private void process(BaseRequest<StatementRequest> systemRequest) {
StatementRequest sdfInfo = systemRequest.getRequestPayload();
SDf01 sdf01 = sdf01Imdg.getSingleObjectByID(sdfInfo.getSdf01Id());
Company company = companyImdg.getSingleObjectByFieldValues(Map.of("tradingCode", sdf01.getDeal()));
if (company == null) {
errorLogger.logError("sdf01.id={}", new EnumMessage(BalanceError.CompanyNotFound), sdfInfo.getSdf01Id());
return;
}
IValidator validator = sDf01Validator.apply(sdf01);
Optional<EnumMessage> error = validator.tillFirstError();
if (BalanceError.AccountNotPresent.equals(error.map(EnumMessage::getSubject).orElse(null))) {
kafkaReqProducer.sendRequestToQueue(Consts.ACCOUNT_NEW, createAccountRequest(sdf01.getAccount()));
log.info("account not found - send request for creation");
return;
}
if (error.isPresent()) {
errorLogger.logError("sdf01.id={}", error.get(), sdf01.getId());
return;
}
Statement statement = statementImdg.getSingleObjectByFieldValues(Map.of("account", sdf01.getAccount()));
if (statement == null) {
createFlow(sdfInfo, validator.getStored(ValidationStored.Sdf01Account));
} else {
// updateFlow(statement);
}
}
private void createFlow(StatementRequest sdfInfo, Account storedObject) {
}
private AccountNewRequest createAccountRequest(String account) {
AccountNewRequest req = new AccountNewRequest();
req.setAccount(account);
return req;
}
}

View file

@ -0,0 +1,77 @@
package ru.spcex.clearing.balance.validation;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.sdf.SDf01;
import ru.spcex.clearing.balance.config.ImdgValidationContext;
import ru.spcex.clearing.balance.errors.BalanceError;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.platform.enumeration.AccountType;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.validation.IValidationRule;
import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;
public enum Sdf01ValidationRule implements IValidationRule<ImdgValidationContext<SDf01>> {
AccountPresent() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<SDf01> context) {
SDf01 sdf01 = context.getValidatedObject();
Imdg<Account> accountImdg = context.obtainMap(IMDGDistributedNames.Map_Account, Account.class);
Account account = accountImdg.getSingleObjectByFieldValues(Map.of("account", sdf01.getAccount(),
"accountType", AccountType.Clrn.getKey()));
if (account == null) {
return of(BalanceError.AccountNotPresent);
}
context.storeObject(ValidationStored.Sdf01Account, account);
return empty();
}
},
CurrencyCode() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<SDf01> context) {
SDf01 sdf01 = context.getValidatedObject();
if (!"RUR".equals(sdf01.getCurr_code())) {
return of(BalanceError.CurrencyNotFound);
}
return empty();
}
},
CurrentDateOnly() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<SDf01> context) {
SDf01 sdf01 = context.getValidatedObject();
//fixme string format???
if (!LocalDate.now().toString().equals(sdf01.getDat())) {
return of(BalanceError.CurrentDateOnly);
}
return empty();
}
},
Market() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<SDf01> context) {
SDf01 sdf01 = context.getValidatedObject();
if (!"U".equals(sdf01.getMarket())) {
return of(BalanceError.WrongMarket);
}
return empty();
}
},
accountType() {
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<SDf01> context) {
SDf01 sdf01 = context.getValidatedObject();
if (!"A".equals(sdf01.getAcc_type())) {
return of(BalanceError.WrongAccount);
}
return empty();
}
};
@Override
public String ruleName() {
return "Sdf01ValidationRule." + name();
}
}

View file

@ -0,0 +1,5 @@
package ru.spcex.clearing.balance.validation;
public enum ValidationStored {
Sdf01Account;
}

View file

@ -3,10 +3,17 @@ balance-service.hazelcast.cluster-members=127.0.0.1
balance-service.hazelcast.login=dev
balance-service.hazelcast.password=dev-pass
balance-service.kafka.bootstrap-servers=localhost:9092
balance-service.kafka.group-id=dev-group
balance-service.kafka.enable-auto-commit=false
balance-service.kafka.session-timeout-ms=30000
balance-service.kafka.auto-offset-reset=latest
balance-service.kafka.linger-ms=1
balance-service.kafka.buffer-memory=33554432
balance-service.kafka-consumer.bootstrap-servers=localhost:9092
balance-service.kafka-consumer.group-id=dev-group
balance-service.kafka-consumer.enable-auto-commit=false
balance-service.kafka-consumer.session-timeout-ms=30000
balance-service.kafka-consumer.auto-offset-reset=latest
balance-service.kafka-consumer.linger-ms=1
balance-service.kafka-consumer.buffer-memory=33554432
balance-service.kafka-producer.bootstrap-servers=localhost:9092
balance-service.kafka-producer.acks=all
balance-service.kafka-producer.retries=0
balance-service.kafka-producer.batch-size=16384
balance-service.kafka-producer.linger-ms=1
balance-service.kafka-producer.buffer-memory=33554432

View file

@ -0,0 +1,2 @@
5211=Company not found
5213=Currency not found

View file

@ -0,0 +1,2 @@
5211=Компания не найдена
5213=Валюта не найдена

View file

@ -0,0 +1,18 @@
package ru.spcex.platform.enumeration;
import ru.spcex.platform.utils.enumeration.IEnumKey;
public enum AccountType implements IEnumKey {
Clrn("CLRN");
private final String key;
AccountType(String key) {
this.key = key;
}
@Override
public String getKey() {
return key;
}
}

View file

@ -25,4 +25,9 @@ public interface Consts {
String USER_AUTH_SUCCESS = "user-auth-success";
String USER_LOGOUT_SUCCESS = "user-logout-success";
//todo
String STATEMENT_NEW = "statement-action";
String ACCOUNT_NEW = "account-new";
}

View file

@ -0,0 +1,14 @@
package ru.spcex.clearing.platform.messaging.domain.cud.account;
public class AccountNewRequest {
private String account;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}

View file

@ -0,0 +1,16 @@
package ru.spcex.clearing.platform.messaging.domain.cud.balance;
import com.fasterxml.jackson.annotation.JsonProperty;
public class StatementRequest {
@JsonProperty
private Long sdf01Id;
public Long getSdf01Id() {
return sdf01Id;
}
public void setSdf01Id(Long sdf01Id) {
this.sdf01Id = sdf01Id;
}
}

View file

@ -35,6 +35,16 @@
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -38,4 +38,8 @@ public interface IValidator {
* валидирует все, возвращает первую ошибку - для совместимости
*/
Optional<EnumMessage> validateAllReturnFirst();
default <S> S getStored(Enum<?> storedObject) {
throw new UnsupportedOperationException("not implemented");
}
}

View file

@ -1,3 +1,7 @@
package ru.spcex.platform.utils.validation;
public interface IValidatorContext {}
public interface IValidatorContext {
default <S> S getStoredObject(Enum<?> storedObject) {
throw new UnsupportedOperationException("not implemented");
}
}

View file

@ -100,6 +100,11 @@ public class ValidatorImpl<C extends IValidatorContext> implements IValidator {
return Optional.empty();
}
@Override
public <S> S getStored(Enum<?> storedObject) {
return context.getStoredObject(storedObject);
}
public C getContext() {
return context;
}

View file

@ -0,0 +1,6 @@
package ru.spcex.platform.utils.validation;
public enum DatePrimitiveValidation {
MonthValue
;
}

View file

@ -0,0 +1,6 @@
package ru.spcex.platform.utils.validation;
public enum DateValidation {
LocalDate, Midnight
;
}

View file

@ -0,0 +1,20 @@
package ru.spcex.platform.utils.validation;
import ru.spcex.platform.utils.enumeration.IEnumId;
public enum TestEnumWithId implements IEnumId {
Message1(1L),
Message2(2L),
Message3(3L),
Message4(4L);
private final Long id;
TestEnumWithId(Long id) {
this.id = id;
}
@Override
public Long getId() {
return id;
}
}

View file

@ -0,0 +1,32 @@
package ru.spcex.platform.utils.validation;
import java.util.HashMap;
import java.util.Map;
public class TestValidationContext<T> implements IValidatorContext {
private Map<Enum<?>, Object> storedObjects;
private T validatedObject;
public TestValidationContext() {
this.storedObjects = new HashMap<>();
}
void setValidatedObject(T validatedObject) {
this.validatedObject = validatedObject;
}
public T getValidatedObject() {
return validatedObject;
}
public void storeObject(Enum<?> storedObject, Object anyObject) {
storedObjects.put(storedObject, anyObject);
}
@Override
@SuppressWarnings("unchecked")
public <S> S getStoredObject(Enum<?> storedObject) {
Object o = storedObjects.get(storedObject);
return (S) o;
}
}

View file

@ -0,0 +1,52 @@
package ru.spcex.platform.utils.validation;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.time.TimeUtil;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Optional;
public enum TestValidationRule implements IValidationRule<TestValidationContext<Instant>> {
isToday() {
@Override
public Optional<EnumMessage> validate(TestValidationContext<Instant> context) {
Instant validatedObject = context.getValidatedObject();
LocalDate localDate = validatedObject.atZone(TimeUtil.zone).toLocalDate();
if (!LocalDate.now().equals(localDate)) {
return of(TestEnumWithId.Message1);
}
context.storeObject(DateValidation.LocalDate, localDate);
return Optional.empty();
}
},
isAfterMidnight() {
@Override
public Optional<EnumMessage> validate(TestValidationContext<Instant> context) {
Instant validatedObject = context.getValidatedObject();
Instant midnight = TimeUtil.localDateToInstant(LocalDate.now());
if (!validatedObject.isAfter(midnight)) {
return of(TestEnumWithId.Message2);
}
context.storeObject(DateValidation.Midnight, midnight);
return empty();
}
},
isMonthValid() {
@Override
public Optional<EnumMessage> validate(TestValidationContext<Instant> context) {
Instant validatedObject = context.getValidatedObject();
LocalDate localDate = validatedObject.atZone(TimeUtil.zone).toLocalDate();
int monthValue = localDate.getMonthValue();
if (!(monthValue >= 1 && monthValue <= 12)) {
return of(TestEnumWithId.Message3);
}
context.storeObject(DatePrimitiveValidation.MonthValue, monthValue);
return empty();
}
};
@Override
public String ruleName() {
return "TestValidationRule" + name();
}
}

View file

@ -0,0 +1,41 @@
package ru.spcex.platform.utils.validation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.time.TimeUtil;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
class ValidatorImplTest {
private IValidator validator;
@BeforeEach
public void initiateValidator() {
TestValidationContext<Instant> context = new TestValidationContext<>();
context.setValidatedObject(Instant.now());
this.validator = new ValidatorImpl<>(context,
TestValidationRule.isToday,
TestValidationRule.isAfterMidnight,
TestValidationRule.isMonthValid
);
}
@Test
public void testStoredObjects() {
Optional<EnumMessage> enumMessage = validator.tillFirstError();
assertTrue(enumMessage.isEmpty());
LocalDate localDate = validator.getStored(DateValidation.LocalDate);
assertNotNull(localDate);
assertEquals(LocalDate.now(), localDate);
Instant midnight = validator.getStored(DateValidation.Midnight);
assertNotNull(midnight);
assertEquals(LocalDate.now().atStartOfDay().atZone(TimeUtil.zone).toInstant(), midnight);
Integer month = validator.getStored(DatePrimitiveValidation.MonthValue);
assertNotNull(month);
assertEquals(LocalDate.now().getMonthValue(), month);
}
}