Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import site.campingon.campingon.camp.service.mongodb.SearchInfoService;
import site.campingon.campingon.common.jwt.CustomUserDetails;
import site.campingon.campingon.common.util.AuthenticateUser;
import site.campingon.campingon.user.service.UserService;

import java.util.List;


@Slf4j
Expand Down Expand Up @@ -60,4 +61,13 @@ public ResponseEntity<Page<CampListResponseDto>> getMatchedCamps(
userDetails.getName(), userDetails.getId(), PageRequest.of(page, size))
);
}

// 검색어 자동완성
@GetMapping("/autocomplete")
public ResponseEntity<List<String>> getAutocompleteResults(
@RequestParam(name = "word") String word
) {
return ResponseEntity.ok(searchInfoService.getAutocompleteResults(word));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
@RequiredArgsConstructor
public class MongoSearchClient {
private final MongoTemplate mongoTemplate;
private static final String INDEX_NAME = "searchIndex";
private static final String SEARCH_INDEX = "searchIndex";
private static final String AUTOCOMPLETE_INDEX = "autocompleteIndex";
private static final String COLLECTION_NAME = "search_info";

private static final String PROJECT_STAGE = "{" +
Expand All @@ -39,9 +40,17 @@ public SearchResultDto searchWithUserPreferences(String searchTerm, List<String>
String mustClause = "";
if (StringUtils.hasText(city)) {
List<String> cityVariants = getCityVariants(city);
List<String> phrases = cityVariants.stream()
List<String> phrases = new ArrayList<>();

// city 검색을 위한 phrases
phrases.addAll(cityVariants.stream()
.map(variant -> "{phrase: {query: '" + variant + "', path: 'address.city'}}")
.collect(Collectors.toList());
.collect(Collectors.toList()));

// state에서 제주시 추가 검색
if (city.equals("제주특별자치도")) {
phrases.add("{phrase: {query: '제주시', path: 'address.state'}}");
}

mustClause = ", must: [{compound: {should: [" + String.join(",", phrases) + "]}}]";
}
Expand All @@ -56,7 +65,7 @@ public SearchResultDto searchWithUserPreferences(String searchTerm, List<String>
"%s" + // must clause를 조건부로 추가
"}" +
"}}",
INDEX_NAME,
SEARCH_INDEX,
createShouldClauses(searchTerm, userKeywords),
mustClause
);
Expand Down Expand Up @@ -96,6 +105,9 @@ private String createShouldClauses(String searchTerm, List<String> userKeywords)
clauses.add("{text: {query: '" + searchTerm + "', path: 'address.state', score: {boost: {value: 3}}, fuzzy: {maxEdits: 1}}}");
clauses.add("{text: {query: '" + searchTerm + "', path: 'address.city', score: {boost: {value: 2}}}}");
clauses.add("{text: {query: '" + searchTerm + "', path: 'intro', score: {boost: {value: 2}}}}");


//clauses.add("{regex: {query: '" + searchTerm + "', path: 'name', allowAnalyzedField: true, score: {boost: {value: 3.5}}}}");
}

if (userKeywords != null && !userKeywords.isEmpty()) {
Expand Down Expand Up @@ -170,13 +182,56 @@ else if (city.endsWith("도")) {
variants.add(base); // 경기
}

// 특별자치도 케이스 (제주)
// 특별자치도 케이스 (제주, 강원)
else if (city.endsWith("특별자치도")) {
String base = city.replace("특별자치도", "");
variants.add(base); // 제주
variants.add(base + "도"); // 제주도
variants.add(base); // 제주
variants.add(base + "도"); // 제주도
/*variants.add(base + "시"); // 제주시
variants.add(base + "특별시"); // 제주특별시
variants.add(base + "특별자치시"); // 제주특별자치시*/
}

return variants;
}


// 검색어 자동완성
public List<String> getAutocompleteResults(String word) {
String searchQuery = String.format(
"{$search: {" +
"index: '%s'," +
"autocomplete: {" +
"query: '%s'," +
"path: 'name'," +
"fuzzy: {maxEdits: 1}" +
"}" +
"}}",
AUTOCOMPLETE_INDEX,
word
);

String projectStage = "{$project: {name: 1}}"; // 이름만
String limitStage = "{$limit: 8}"; // 자동 검색란은 6개까지만

AggregationOperation searchOperation = context -> Document.parse(searchQuery);
AggregationOperation projectOperation = context -> Document.parse(projectStage);
AggregationOperation limitOperation = context -> Document.parse(limitStage);

List<Document> results = mongoTemplate.aggregate(
Aggregation.newAggregation(
searchOperation,
projectOperation,
limitOperation
),
COLLECTION_NAME,
Document.class
).getMappedResults();

return results.stream()
.map(doc -> doc.getString("name"))
.distinct()
.collect(Collectors.toList());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@
public class CampService {

private final CampRepository campRepository;
private final MongoSearchClient searchInfoRepositoryImpl;
private final UserKeywordRepository userKeywordRepository;
private final BookmarkRepository bookMarkRepository;
private final CampMapper campMapper;


// 인기 캠핑장 조회
public Page<CampListResponseDto> getPopularCamps(Long userId, Pageable pageable) {
return campRepository.findPopularCamps(pageable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public Page<CampListResponseDto> searchExactMatchBySearchTermAndUserKeyword(
if (userId != 0L) {
dto.setMarked(bookmarkRepository.existsByCampIdAndUserId(searchInfo.getCampId(), userId));
}

return dto;
})
.collect(Collectors.toList());
Expand Down Expand Up @@ -97,4 +98,12 @@ public Page<CampListResponseDto> getMatchedCampsByKeywords(

return new PageImpl<>(dtoList, pageable, searchResult.getTotal());
}

// 검색어 자동완성
public List<String> getAutocompleteResults(String word) {
if (!StringUtils.hasText(word) || word.length() < 3) {
return new ArrayList<>();
}
return mongoSearchClient.getAutocompleteResults(word);
}
}
1 change: 1 addition & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ spring:
data:
mongodb:
uri: mongodb+srv://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@${MONGODB_URL}/${MONGODB_NAME}?retryWrites=true&w=majority&appName=CampingOn
auto-index-creation: true
redis:
host: campingon-redis
port: 6379
Expand Down
Loading