Merge pull request #1 from uPagge/develop

Develop
This commit is contained in:
Struchkov Mark 2019-07-08 00:12:23 +03:00 committed by GitHub
commit d383ad6c21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 151 additions and 129 deletions

11
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>org.sadtech.autoresponder</groupId> <groupId>org.sadtech.autoresponder</groupId>
<artifactId>autoresponder</artifactId> <artifactId>autoresponder</artifactId>
<version>1.6.2-RELEASE</version> <version>1.6.3-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<build> <build>
<plugins> <plugins>
@ -24,6 +24,7 @@
<properties> <properties>
<slf4j.ver>1.7.26</slf4j.ver> <slf4j.ver>1.7.26</slf4j.ver>
<junit.ver>4.12</junit.ver>
</properties> </properties>
<dependencies> <dependencies>
@ -36,7 +37,13 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version> <version>${junit.ver}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,13 +1,16 @@
package org.sadtech.autoresponder; package org.sadtech.autoresponder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.sadtech.autoresponder.compare.UnitPriorityComparator; import org.sadtech.autoresponder.compare.UnitPriorityComparator;
import org.sadtech.autoresponder.entity.Unit; import org.sadtech.autoresponder.entity.Unit;
import org.sadtech.autoresponder.entity.UnitPointer; import org.sadtech.autoresponder.entity.UnitPointer;
import org.sadtech.autoresponder.exception.NotFoundUnitException; import org.sadtech.autoresponder.exception.NotFoundUnitException;
import org.sadtech.autoresponder.service.UnitPointerService; import org.sadtech.autoresponder.service.UnitPointerService;
import org.sadtech.autoresponder.util.Description;
import org.sadtech.autoresponder.util.Parser; import org.sadtech.autoresponder.util.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
@ -15,16 +18,26 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/* /**
Отвечает за функционирование автоответчика * Реализуют основную логику автоответчика.
*
* @author upagge [07/07/2019]
*/ */
@Slf4j
public class Autoresponder { public class Autoresponder {
private static final Logger log = LoggerFactory.getLogger(Autoresponder.class); @Description("Компоратор для сортировки Unit-ов")
private static final UnitPriorityComparator UNIT_PRIORITY_COMPARATOR = new UnitPriorityComparator(); private static final UnitPriorityComparator UNIT_PRIORITY_COMPARATOR = new UnitPriorityComparator();
@Description("Начальные юниты, первый запрос приходит на них")
private final Set<Unit> startUnits; private final Set<Unit> startUnits;
@Getter
@Setter
@Description("Дефолтный юнит, отправляется если ни один Unit не подошел")
private Unit defaultUnit; private Unit defaultUnit;
@Getter
private final UnitPointerService unitPointerService; private final UnitPointerService unitPointerService;
public Autoresponder(UnitPointerService unitPointerService, Set<Unit> startUnits) { public Autoresponder(UnitPointerService unitPointerService, Set<Unit> startUnits) {
@ -32,19 +45,14 @@ public class Autoresponder {
this.unitPointerService = unitPointerService; this.unitPointerService = unitPointerService;
} }
public UnitPointerService getUnitPointerService() { /**
return unitPointerService; * Принимает текстовый запрос пользователя и отдает юнит, который соответствует запросу
} *
* @param personId Идентификатор пользователя
public void setDefaultUnit(Unit defaultUnit) { * @param message Запрос пользователя - текстовое сообщение
this.defaultUnit = defaultUnit; * @return {@link Unit}, который отвечает за данные для обработки данного запроса
} */
public Unit answer(@NonNull Integer personId, String message) {
public Unit getDefaultUnit() {
return defaultUnit;
}
public Unit answer(Integer personId, String message) {
UnitPointer unitPointer = checkAndAddPerson(personId); UnitPointer unitPointer = checkAndAddPerson(personId);
Unit unit; Unit unit;
try { try {
@ -60,6 +68,13 @@ public class Autoresponder {
return Optional.ofNullable(unit).orElseThrow(NotFoundUnitException::new); return Optional.ofNullable(unit).orElseThrow(NotFoundUnitException::new);
} }
/**
* Выбирает, какой {@link Unit} будет отдан для обработки
*
* @param nextUnits Множество следующих Unit-ов
* @param message Запрос пользователя - текстовое сообщение
* @return Юнит, который нуждается в обработке в соответствии с запросом пользователя
*/
private Unit nextUnit(Set<Unit> nextUnits, String message) { private Unit nextUnit(Set<Unit> nextUnits, String message) {
Optional<Unit> unit = nextUnits.stream() Optional<Unit> unit = nextUnits.stream()
.filter(nextUnit -> nextUnit.getPattern() != null && patternReg(nextUnit, message)) .filter(nextUnit -> nextUnit.getPattern() != null && patternReg(nextUnit, message))
@ -79,6 +94,12 @@ public class Autoresponder {
return unit.orElseThrow(NotFoundUnitException::new); return unit.orElseThrow(NotFoundUnitException::new);
} }
/**
* Проверяет наличие {@link UnitPointer} у пользовтеля и создает его, если не находит
*
* @param personId Идентификатор пользователя
* @return {@link UnitPointer}, который сохраняет {@link Unit}, который был обработан последним
*/
private UnitPointer checkAndAddPerson(Integer personId) { private UnitPointer checkAndAddPerson(Integer personId) {
UnitPointer unitPointer; UnitPointer unitPointer;
if (unitPointerService.check(personId)) { if (unitPointerService.check(personId)) {
@ -90,6 +111,7 @@ public class Autoresponder {
return unitPointer; return unitPointer;
} }
private boolean patternReg(Unit unit, String message) { private boolean patternReg(Unit unit, String message) {
Pattern pattern = unit.getPattern(); Pattern pattern = unit.getPattern();
Matcher m = pattern.matcher(message); Matcher m = pattern.matcher(message);

View File

@ -4,6 +4,11 @@ import org.sadtech.autoresponder.entity.Unit;
import java.util.Comparator; import java.util.Comparator;
/**
* Компоратор для сортировки {@link Unit} по приоритету.
*
* @author upagge [07/07/2019]
*/
public class UnitPriorityComparator implements Comparator<Unit> { public class UnitPriorityComparator implements Comparator<Unit> {
@Override @Override

View File

@ -1,20 +1,40 @@
package org.sadtech.autoresponder.entity; package org.sadtech.autoresponder.entity;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.sadtech.autoresponder.util.Description;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/* /**
Абстрактный класс, предпологающий какие-то действия * Абстрактная сущность, отвечающая за хранение данных, необходимая для обработки запроса.
*
* @author upagge [07/07/2019]
*/ */
@Getter
@EqualsAndHashCode
public abstract class Unit { public abstract class Unit {
@Description("Ключевые слова")
private Set<String> keyWords; private Set<String> keyWords;
@Setter
@Description("Регулярное выражение")
private Pattern pattern; private Pattern pattern;
@Setter
// todo [upagge] [07/07/2019]: Придумать нормальное описание
private Integer matchThreshold = 10; private Integer matchThreshold = 10;
@Setter
@Description("Значение приоритета")
private Integer priority = 10; private Integer priority = 10;
@Description("Множество следующих Unit в сценарии")
private Set<Unit> nextUnits; private Set<Unit> nextUnits;
public void setKeyWord(String... keyWord) { public void setKeyWord(String... keyWord) {
@ -31,10 +51,6 @@ public abstract class Unit {
this.keyWords.addAll(keyWords); this.keyWords.addAll(keyWords);
} }
public Set<String> getKeyWords() {
return keyWords;
}
public void setNextUnit(Unit... unit) { public void setNextUnit(Unit... unit) {
if (nextUnits == null) { if (nextUnits == null) {
nextUnits = new HashSet<>(); nextUnits = new HashSet<>();
@ -49,48 +65,4 @@ public abstract class Unit {
this.nextUnits.addAll(nextUnits); this.nextUnits.addAll(nextUnits);
} }
public Set<Unit> getNextUnits() {
return nextUnits;
}
public Integer getMatchThreshold() {
return matchThreshold;
}
public void setMatchThreshold(Integer matchThreshold) {
this.matchThreshold = matchThreshold;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Unit)) return false;
Unit unit = (Unit) o;
return Objects.equals(keyWords, unit.keyWords) &&
Objects.equals(pattern, unit.pattern) &&
Objects.equals(matchThreshold, unit.matchThreshold) &&
Objects.equals(priority, unit.priority) &&
Objects.equals(nextUnits, unit.nextUnits);
}
@Override
public int hashCode() {
return Objects.hash(keyWords, pattern, matchThreshold, priority, nextUnits);
}
} }

View File

@ -1,59 +1,26 @@
package org.sadtech.autoresponder.entity; package org.sadtech.autoresponder.entity;
import java.util.Objects; import lombok.AllArgsConstructor;
import lombok.Data;
import org.sadtech.autoresponder.util.Description;
/* /**
Сохраняет юнит, на котором остановился пользователь. * Сущность для сохранения позиции пользователя в сценарии, состоящем из связного списка элементов {@link Unit}.
*
* @author upagge [07/07/2019]
*/ */
@Data
@AllArgsConstructor
public class UnitPointer { public class UnitPointer {
@Description("Идентификатор пользователя")
private Integer entityId; private Integer entityId;
private Unit unit;
public UnitPointer(Integer entityId, Unit unit) { @Description("Юнит, который был обработан")
this.entityId = entityId; private Unit unit;
this.unit = unit;
}
public UnitPointer(Integer entityId) { public UnitPointer(Integer entityId) {
this.entityId = entityId; this.entityId = entityId;
} }
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
}
public Unit getUnit() {
return unit;
}
public void setUnit(Unit unit) {
this.unit = unit;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UnitPointer unitPointer = (UnitPointer) o;
return Objects.equals(entityId, unitPointer.entityId) &&
Objects.equals(unit, unitPointer.unit);
}
@Override
public int hashCode() {
return Objects.hash(entityId, unit);
}
@Override
public String toString() {
return "UnitPointer{" +
"entityId=" + entityId +
", unit=" + unit +
'}';
}
} }

View File

@ -1,4 +1,9 @@
package org.sadtech.autoresponder.exception; package org.sadtech.autoresponder.exception;
/**
* Исключение, которое выбрасывается, если юнит не найден.
*
* @author upagge [07/07/2019]
*/
public class NotFoundUnitException extends RuntimeException { public class NotFoundUnitException extends RuntimeException {
} }

View File

@ -4,6 +4,11 @@ import org.sadtech.autoresponder.entity.UnitPointer;
import java.util.Collection; import java.util.Collection;
/**
* Интегрфейс для работы с хранилищем сущности {@link UnitPointer}.
*
* @author upagge [07/07/2019]
*/
public interface UnitPointerRepository { public interface UnitPointerRepository {
void add(UnitPointer unitPointer); void add(UnitPointer unitPointer);
@ -12,8 +17,12 @@ public interface UnitPointerRepository {
void remove(Integer entityId); void remove(Integer entityId);
void addAll(Collection<UnitPointer> unitPointerMap); void addAll(Collection<UnitPointer> unitPointers);
/**
* @param entityId Идентификатор пользователя
* @return Объект с последним обработанным {@link org.sadtech.autoresponder.entity.Unit} для пользователя
*/
UnitPointer findByEntityId(Integer entityId); UnitPointer findByEntityId(Integer entityId);
} }

View File

@ -6,6 +6,11 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* Реализация хранилища для {@link UnitPointer} на основе Map.
*
* @author upagge [07/07/2019]
*/
public class UnitPointerRepositoryMap implements UnitPointerRepository { public class UnitPointerRepositoryMap implements UnitPointerRepository {
private Map<Integer, UnitPointer> unitPointerMap = new HashMap<>(); private Map<Integer, UnitPointer> unitPointerMap = new HashMap<>();

View File

@ -3,10 +3,20 @@ package org.sadtech.autoresponder.service;
import org.sadtech.autoresponder.entity.Unit; import org.sadtech.autoresponder.entity.Unit;
import org.sadtech.autoresponder.entity.UnitPointer; import org.sadtech.autoresponder.entity.UnitPointer;
/**
* Сервис для взаимодействия с сущностью {@link UnitPointer}.
*
* @author upagge [07/07/2019]
*/
public interface UnitPointerService { public interface UnitPointerService {
void add(UnitPointer unitPointer); void add(UnitPointer unitPointer);
/**
* Проверка наличия {@link UnitPointer} для пользователя
* @param entityId Идентификатор пользователя
* @return true - если найдено
*/
boolean check(Integer entityId); boolean check(Integer entityId);
UnitPointer getByEntityId(Integer entityId); UnitPointer getByEntityId(Integer entityId);

View File

@ -1,16 +1,14 @@
package org.sadtech.autoresponder.service; package org.sadtech.autoresponder.service;
import lombok.extern.slf4j.Slf4j;
import org.sadtech.autoresponder.entity.Unit; import org.sadtech.autoresponder.entity.Unit;
import org.sadtech.autoresponder.entity.UnitPointer; import org.sadtech.autoresponder.entity.UnitPointer;
import org.sadtech.autoresponder.repository.UnitPointerRepository; import org.sadtech.autoresponder.repository.UnitPointerRepository;
import org.sadtech.autoresponder.repository.UnitPointerRepositoryMap; import org.sadtech.autoresponder.repository.UnitPointerRepositoryMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Slf4j
public class UnitPointerServiceImpl implements UnitPointerService { public class UnitPointerServiceImpl implements UnitPointerService {
private static final Logger log = LoggerFactory.getLogger(UnitPointerServiceImpl.class);
private UnitPointerRepository unitPointerRepository; private UnitPointerRepository unitPointerRepository;
public UnitPointerServiceImpl() { public UnitPointerServiceImpl() {

View File

@ -0,0 +1,17 @@
package org.sadtech.autoresponder.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Служит для описания роли полей в классах.
*
* @author upagge [07/07/2019]
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Description {
String value();
}

View File

@ -1,5 +1,6 @@
package org.sadtech.autoresponder.util; package org.sadtech.autoresponder.util;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -7,11 +8,14 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/* /**
Возвращает Set слов из текста * Разбивает строку на множество слов, удаляя предлоги.
*
* @author upagge [07/07/2019]
*/ */
public class Parser { public class Parser {
@Description("Множество предлогов")
private static final Set<String> pretexts = Stream private static final Set<String> pretexts = Stream
.of("в", "без", "до", "из", "к", "на", "по", "о", "от", "перед", "при", "с", "у", "за", "над", "об", .of("в", "без", "до", "из", "к", "на", "по", "о", "от", "перед", "при", "с", "у", "за", "над", "об",
"под", "про", "для") "под", "про", "для")
@ -21,13 +25,14 @@ public class Parser {
throw new IllegalStateException("Utility Class"); throw new IllegalStateException("Utility Class");
} }
/**
* Метод по разбиению строки на множество слов
* @param text Строка
* @return Множество слов
*/
public static Set<String> parse(String text) { public static Set<String> parse(String text) {
Pattern p = Pattern.compile("[а-яА-Я0-9]+"); String[] split = text.split("\\P{L}+");
Matcher m = p.matcher(text); Set<String> words = new HashSet<>(Arrays.asList(split));
Set<String> words = new HashSet<>();
while (m.find()) {
words.add(m.group().toLowerCase());
}
words.removeAll(pretexts); words.removeAll(pretexts);
return words; return words;
} }