fix: 鱼类字典相似度查询优化
This commit is contained in:
parent
687ec712e0
commit
f0dcf99d28
@ -74,107 +74,231 @@ public class SdFishDictoryBServiceImpl extends ServiceImpl<SdFishDictoryBMapper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int resultLimit = (limit != null && limit > 0) ? limit : 10;
|
int resultLimit = (limit != null && limit > 0) ? limit : 10;
|
||||||
String searchName = name.trim();
|
String rawSearchName = name.trim();
|
||||||
LambdaQueryWrapper<SdFishDictoryB> wrapper = new LambdaQueryWrapper<>();
|
String searchName = normalizeText(rawSearchName);
|
||||||
wrapper.eq(SdFishDictoryB::getIsDeleted, 0);
|
|
||||||
List<SdFishDictoryB> allFish = list(wrapper);
|
|
||||||
|
|
||||||
SdFishDictoryB exactMatch = allFish.stream()
|
List<SdFishDictoryB> candidateFish = querySimilarFishCandidates(rawSearchName, searchName, resultLimit);
|
||||||
.filter(f -> f.getName() != null && f.getName().equals(searchName))
|
if (candidateFish.isEmpty()) {
|
||||||
.findFirst()
|
return Collections.emptyList();
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (exactMatch != null) {
|
|
||||||
Map<SdFishDictoryB, Integer> similarityMap = new HashMap<>();
|
|
||||||
for (SdFishDictoryB fish : allFish) {
|
|
||||||
if (fish.getId().equals(exactMatch.getId())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int score = calculateSimilarity(exactMatch, fish);
|
|
||||||
if (score > 0) {
|
|
||||||
similarityMap.put(fish, score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SdFishDictoryB> result = similarityMap.entrySet().stream()
|
|
||||||
.sorted(Map.Entry.<SdFishDictoryB, Integer>comparingByValue().reversed())
|
|
||||||
.limit(resultLimit)
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
result.add(0, exactMatch);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SdFishDictoryB> containsMatches = allFish.stream()
|
Map<String, Integer> searchScoreMap = new HashMap<>();
|
||||||
.filter(f -> f.getName() != null && f.getName().contains(searchName))
|
for (SdFishDictoryB fish : candidateFish) {
|
||||||
|
if (fish == null || !StringUtils.hasText(fish.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int score = calculateSearchScore(searchName, fish);
|
||||||
|
if (score > 0) {
|
||||||
|
searchScoreMap.put(fish.getId(), score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchScoreMap.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
SdFishDictoryB referenceFish = candidateFish.stream()
|
||||||
|
.filter(fish -> fish != null && searchScoreMap.containsKey(fish.getId()))
|
||||||
|
.max((f1, f2) -> {
|
||||||
|
int scoreCompare = Integer.compare(searchScoreMap.get(f1.getId()), searchScoreMap.get(f2.getId()));
|
||||||
|
if (scoreCompare != 0) {
|
||||||
|
return scoreCompare;
|
||||||
|
}
|
||||||
|
Integer order1 = f1.getOrderIndex() == null ? 0 : f1.getOrderIndex();
|
||||||
|
Integer order2 = f2.getOrderIndex() == null ? 0 : f2.getOrderIndex();
|
||||||
|
int orderCompare = Integer.compare(order1, order2);
|
||||||
|
if (orderCompare != 0) {
|
||||||
|
return orderCompare;
|
||||||
|
}
|
||||||
|
return safeText(f2.getName()).compareTo(safeText(f1.getName()));
|
||||||
|
})
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (referenceFish == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidateFish.stream()
|
||||||
|
.filter(fish -> fish != null && StringUtils.hasText(fish.getId()))
|
||||||
|
.map(fish -> new AbstractMap.SimpleEntry<>(fish, buildFinalScore(referenceFish, fish, searchScoreMap.getOrDefault(fish.getId(), 0))))
|
||||||
|
.filter(entry -> entry.getValue() > 0)
|
||||||
|
.sorted((e1, e2) -> {
|
||||||
|
int scoreCompare = Integer.compare(e2.getValue(), e1.getValue());
|
||||||
|
if (scoreCompare != 0) {
|
||||||
|
return scoreCompare;
|
||||||
|
}
|
||||||
|
Integer order1 = e1.getKey().getOrderIndex() == null ? 0 : e1.getKey().getOrderIndex();
|
||||||
|
Integer order2 = e2.getKey().getOrderIndex() == null ? 0 : e2.getKey().getOrderIndex();
|
||||||
|
int orderCompare = Integer.compare(order2, order1);
|
||||||
|
if (orderCompare != 0) {
|
||||||
|
return orderCompare;
|
||||||
|
}
|
||||||
|
return safeText(e1.getKey().getName()).compareTo(safeText(e2.getKey().getName()));
|
||||||
|
})
|
||||||
|
.limit(resultLimit)
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
if (!containsMatches.isEmpty()) {
|
private List<SdFishDictoryB> querySimilarFishCandidates(String rawSearchName, String normalizedSearchName, int resultLimit) {
|
||||||
List<SdFishDictoryB> result = containsMatches.stream()
|
int candidateLimit = Math.min(Math.max(resultLimit * 30, 120), 500);
|
||||||
.sorted((f1, f2) -> {
|
LinkedHashMap<String, SdFishDictoryB> candidateMap = new LinkedHashMap<>();
|
||||||
int len1 = f1.getName().length();
|
|
||||||
int len2 = f2.getName().length();
|
mergeCandidates(candidateMap, queryExactCandidates(rawSearchName, normalizedSearchName, candidateLimit));
|
||||||
if (len1 != len2) {
|
if (candidateMap.size() < candidateLimit) {
|
||||||
return Integer.compare(len1, len2);
|
mergeCandidates(candidateMap, queryContainsCandidates(rawSearchName, normalizedSearchName, candidateLimit));
|
||||||
}
|
}
|
||||||
return f1.getName().compareTo(f2.getName());
|
if (candidateMap.size() < candidateLimit) {
|
||||||
})
|
mergeCandidates(candidateMap, queryTokenCandidates(normalizedSearchName, candidateLimit));
|
||||||
.limit(resultLimit)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SdFishDictoryB partialMatch = allFish.stream()
|
return new ArrayList<>(candidateMap.values());
|
||||||
.filter(f -> f.getName() != null && (searchName.contains(f.getName()) || f.getName().contains(searchName)))
|
}
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (partialMatch != null) {
|
private void mergeCandidates(Map<String, SdFishDictoryB> candidateMap, List<SdFishDictoryB> candidates) {
|
||||||
Map<SdFishDictoryB, Integer> similarityMap = new HashMap<>();
|
if (candidates == null || candidates.isEmpty()) {
|
||||||
for (SdFishDictoryB fish : allFish) {
|
return;
|
||||||
if (fish.getId().equals(partialMatch.getId())) {
|
}
|
||||||
continue;
|
for (SdFishDictoryB fish : candidates) {
|
||||||
}
|
if (fish == null || !StringUtils.hasText(fish.getId())) {
|
||||||
int score = calculateSimilarity(partialMatch, fish);
|
continue;
|
||||||
if (score > 0) {
|
|
||||||
similarityMap.put(fish, score);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
candidateMap.putIfAbsent(fish.getId(), fish);
|
||||||
List<SdFishDictoryB> result = similarityMap.entrySet().stream()
|
|
||||||
.sorted(Map.Entry.<SdFishDictoryB, Integer>comparingByValue().reversed())
|
|
||||||
.limit(resultLimit)
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
result.add(0, partialMatch);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
private List<SdFishDictoryB> queryExactCandidates(String rawSearchName, String normalizedSearchName, int limit) {
|
||||||
|
if (!StringUtils.hasText(rawSearchName)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<SdFishDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||||
|
wrapper.and(w -> w
|
||||||
|
.eq(SdFishDictoryB::getName, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getAlias, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getNameEn, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getSpecies, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getGenus, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getFamily, rawSearchName)
|
||||||
|
.or().eq(SdFishDictoryB::getOrders, rawSearchName)
|
||||||
|
.or().apply("LOWER(NAME) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(ALIAS) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(NAME_EN) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(SPECIES) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(GENUS) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(FAMILY) = {0}", normalizedSearchName)
|
||||||
|
.or().apply("LOWER(ORDERS) = {0}", normalizedSearchName));
|
||||||
|
wrapper.orderByDesc(SdFishDictoryB::getOrderIndex);
|
||||||
|
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SdFishDictoryB> queryContainsCandidates(String rawSearchName, String normalizedSearchName, int limit) {
|
||||||
|
if (!StringUtils.hasText(rawSearchName)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
String likeValue = "%" + rawSearchName + "%";
|
||||||
|
String normalizedLikeValue = "%" + normalizedSearchName + "%";
|
||||||
|
LambdaQueryWrapper<SdFishDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||||
|
wrapper.and(w -> w
|
||||||
|
.like(SdFishDictoryB::getName, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getAlias, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getNameEn, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getSpecies, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getGenus, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getFamily, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getOrders, rawSearchName)
|
||||||
|
.or().like(SdFishDictoryB::getIntroduce, rawSearchName)
|
||||||
|
.or().apply("LOWER(NAME) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(ALIAS) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(NAME_EN) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(SPECIES) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(GENUS) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(FAMILY) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(ORDERS) LIKE {0}", normalizedLikeValue)
|
||||||
|
.or().apply("LOWER(INTRODUCE) LIKE {0}", normalizedLikeValue));
|
||||||
|
wrapper.orderByDesc(SdFishDictoryB::getOrderIndex);
|
||||||
|
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SdFishDictoryB> queryTokenCandidates(String normalizedSearchName, int limit) {
|
||||||
|
Set<String> tokens = splitTokens(normalizedSearchName).stream()
|
||||||
|
.filter(StringUtils::hasText)
|
||||||
|
.filter(token -> token.length() >= 2)
|
||||||
|
.limit(5)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
if (tokens.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<SdFishDictoryB> wrapper = buildCandidateSelectWrapper();
|
||||||
|
wrapper.and(w -> {
|
||||||
|
boolean first = true;
|
||||||
|
for (String token : tokens) {
|
||||||
|
String likeValue = "%" + token + "%";
|
||||||
|
if (!first) {
|
||||||
|
w.or();
|
||||||
|
}
|
||||||
|
w.apply("LOWER(NAME) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(ALIAS) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(NAME_EN) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(SPECIES) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(GENUS) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(FAMILY) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(ORDERS) LIKE {0}", likeValue)
|
||||||
|
.or().apply("LOWER(INTRODUCE) LIKE {0}", likeValue);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
wrapper.orderByDesc(SdFishDictoryB::getOrderIndex);
|
||||||
|
return page(new Page<>(1, limit), wrapper).getRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<SdFishDictoryB> buildCandidateSelectWrapper() {
|
||||||
|
LambdaQueryWrapper<SdFishDictoryB> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.select(
|
||||||
|
SdFishDictoryB::getId,
|
||||||
|
SdFishDictoryB::getCode,
|
||||||
|
SdFishDictoryB::getName,
|
||||||
|
SdFishDictoryB::getNameEn,
|
||||||
|
SdFishDictoryB::getAlias,
|
||||||
|
SdFishDictoryB::getIntroduce,
|
||||||
|
SdFishDictoryB::getOrders,
|
||||||
|
SdFishDictoryB::getFamily,
|
||||||
|
SdFishDictoryB::getGenus,
|
||||||
|
SdFishDictoryB::getSpecies,
|
||||||
|
SdFishDictoryB::getType,
|
||||||
|
SdFishDictoryB::getHabitat,
|
||||||
|
SdFishDictoryB::getHabitMigrat,
|
||||||
|
SdFishDictoryB::getFeedingHabit,
|
||||||
|
SdFishDictoryB::getSpawnCharact,
|
||||||
|
SdFishDictoryB::getPtype,
|
||||||
|
SdFishDictoryB::getResourceType,
|
||||||
|
SdFishDictoryB::getRare,
|
||||||
|
SdFishDictoryB::getOrderIndex,
|
||||||
|
SdFishDictoryB::getIsDeleted
|
||||||
|
);
|
||||||
|
wrapper.eq(SdFishDictoryB::getIsDeleted, 0);
|
||||||
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calculateSimilarity(SdFishDictoryB ref, SdFishDictoryB target) {
|
private int calculateSimilarity(SdFishDictoryB ref, SdFishDictoryB target) {
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
if (ref.getGenus() != null && ref.getGenus().equals(target.getGenus()) && !ref.getGenus().isEmpty()) {
|
if (ref == null || target == null) {
|
||||||
score += 25;
|
return 0;
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(ref.getId()) && ref.getId().equals(target.getId())) {
|
||||||
|
return 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ref.getSpecies() != null && ref.getSpecies().equals(target.getSpecies()) && !ref.getSpecies().isEmpty()) {
|
score += calculateFieldSimilarity(ref.getName(), target.getName(), 45);
|
||||||
score += 20;
|
score += calculateFieldSimilarity(ref.getNameEn(), target.getNameEn(), 22);
|
||||||
}
|
score += calculateFieldSimilarity(ref.getAlias(), target.getAlias(), 26);
|
||||||
|
score += calculateFieldSimilarity(ref.getSpecies(), target.getSpecies(), 30);
|
||||||
|
score += calculateFieldSimilarity(ref.getGenus(), target.getGenus(), 24);
|
||||||
|
score += calculateFieldSimilarity(ref.getFamily(), target.getFamily(), 18);
|
||||||
|
score += calculateFieldSimilarity(ref.getOrders(), target.getOrders(), 14);
|
||||||
|
score += calculateFieldSimilarity(ref.getIntroduce(), target.getIntroduce(), 8);
|
||||||
|
|
||||||
if (ref.getFamily() != null && ref.getFamily().equals(target.getFamily()) && !ref.getFamily().isEmpty()) {
|
if (ref.getType() != null && ref.getType().equals(target.getType()) && !ObjectUtil.isEmpty(ref.getType())) {
|
||||||
score += 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref.getOrders() != null && ref.getOrders().equals(target.getOrders()) && !ref.getOrders().isEmpty()) {
|
|
||||||
score += 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref.getType() != null && ref.getType().equals(target.getType()) && !ObjectUtil.isEmpty(ref.getType()) ) {
|
|
||||||
score += 5;
|
score += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,4 +332,113 @@ public class SdFishDictoryBServiceImpl extends ServiceImpl<SdFishDictoryBMapper,
|
|||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int calculateSearchScore(String searchName, SdFishDictoryB fish) {
|
||||||
|
int score = 0;
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getName(), 220, 140, 90, 40);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getAlias(), 180, 120, 80, 30);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getNameEn(), 160, 100, 70, 25);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getSpecies(), 140, 90, 60, 25);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getGenus(), 120, 80, 55, 20);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getFamily(), 100, 70, 45, 18);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getOrders(), 90, 60, 40, 15);
|
||||||
|
score += calculateFieldSearchScore(searchName, fish.getIntroduce(), 45, 30, 0, 12);
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int buildFinalScore(SdFishDictoryB referenceFish, SdFishDictoryB currentFish, int searchScore) {
|
||||||
|
int totalScore = searchScore;
|
||||||
|
if (referenceFish != null) {
|
||||||
|
totalScore += calculateSimilarity(referenceFish, currentFish);
|
||||||
|
}
|
||||||
|
return totalScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateFieldSearchScore(String searchText, String candidate, int exactScore, int containsScore,
|
||||||
|
int reverseContainsScore, int overlapMaxScore) {
|
||||||
|
String normalizedSearch = normalizeText(searchText);
|
||||||
|
String normalizedCandidate = normalizeText(candidate);
|
||||||
|
if (!StringUtils.hasText(normalizedSearch) || !StringUtils.hasText(normalizedCandidate)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (normalizedCandidate.equals(normalizedSearch)) {
|
||||||
|
return exactScore;
|
||||||
|
}
|
||||||
|
if (normalizedCandidate.contains(normalizedSearch)) {
|
||||||
|
return containsScore;
|
||||||
|
}
|
||||||
|
if (reverseContainsScore > 0 && normalizedSearch.contains(normalizedCandidate) && normalizedCandidate.length() >= 2) {
|
||||||
|
return reverseContainsScore;
|
||||||
|
}
|
||||||
|
return calculateTextOverlapScore(normalizedSearch, normalizedCandidate, overlapMaxScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateFieldSimilarity(String left, String right, int exactScore) {
|
||||||
|
String normalizedLeft = normalizeText(left);
|
||||||
|
String normalizedRight = normalizeText(right);
|
||||||
|
if (!StringUtils.hasText(normalizedLeft) || !StringUtils.hasText(normalizedRight)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (normalizedLeft.equals(normalizedRight)) {
|
||||||
|
return exactScore;
|
||||||
|
}
|
||||||
|
if (normalizedLeft.contains(normalizedRight) || normalizedRight.contains(normalizedLeft)) {
|
||||||
|
return Math.max(1, exactScore / 2);
|
||||||
|
}
|
||||||
|
return calculateTextOverlapScore(normalizedLeft, normalizedRight, Math.max(1, exactScore / 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateTextOverlapScore(String left, String right, int maxScore) {
|
||||||
|
if (!StringUtils.hasText(left) || !StringUtils.hasText(right) || maxScore <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Set<String> leftTokens = splitTokens(left);
|
||||||
|
Set<String> rightTokens = splitTokens(right);
|
||||||
|
if (leftTokens.isEmpty() || rightTokens.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
long commonCount = leftTokens.stream().filter(rightTokens::contains).count();
|
||||||
|
if (commonCount <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int base = Math.min(leftTokens.size(), rightTokens.size());
|
||||||
|
if (base <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int) Math.min(maxScore, Math.round((double) commonCount * maxScore / base));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> splitTokens(String text) {
|
||||||
|
String normalized = normalizeText(text);
|
||||||
|
if (!StringUtils.hasText(normalized)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
Set<String> tokens = new LinkedHashSet<>();
|
||||||
|
String[] parts = normalized.split("[\\s,,;;、/\\\\()()]+");
|
||||||
|
for (String part : parts) {
|
||||||
|
if (StringUtils.hasText(part)) {
|
||||||
|
tokens.add(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tokens.isEmpty()) {
|
||||||
|
for (int i = 0; i < normalized.length(); i++) {
|
||||||
|
char ch = normalized.charAt(i);
|
||||||
|
if (!Character.isWhitespace(ch)) {
|
||||||
|
tokens.add(String.valueOf(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeText(String text) {
|
||||||
|
if (!StringUtils.hasText(text)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text.trim().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeText(String text) {
|
||||||
|
return text == null ? "" : text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user