Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
98b3ae3
feature(search): elasticsearch 의존성 추가
ayoung-dev Dec 8, 2024
65bc207
feature(search): elasticsearch document, repository 생성
ayoung-dev Dec 8, 2024
ca23f96
feature(search): repository에 elasticsearch 관련 메서드 추가
ayoung-dev Dec 8, 2024
28e2990
feature(search): service 추가
ayoung-dev Dec 8, 2024
ea3d75b
feature(search): controller 키워드 검색 추가
ayoung-dev Dec 8, 2024
53533f1
feature(search): elasticsearch 저장 스케쥴러 추가
ayoung-dev Dec 8, 2024
27994bb
feature(search): elasticsearch config 추가
ayoung-dev Dec 8, 2024
0a99d88
feature(search): community 게시글 fixture 추가
ayoung-dev Dec 8, 2024
aa4c16d
test(search): community 게시글 검색 테스트 추가
ayoung-dev Dec 8, 2024
4d59d93
test(search): recruit 모집글 검색 테스트 추가
ayoung-dev Dec 8, 2024
3fa8e8e
test(search): import 경로 수정
ayoung-dev Dec 8, 2024
f1fc7d6
refactor(search): scheduler 수정
ayoung-dev Dec 8, 2024
8cb0b02
test(search): 코드 리뷰 사항 반영
ayoung-dev Dec 8, 2024
ffd2769
feature(search): 검색 keyword가 null인 경우 전체 조회
ayoung-dev Dec 8, 2024
102b9e2
feature(search): 위치 기반 검색 추가 및 키워드 condition에 추가
ayoung-dev Dec 8, 2024
b78814c
test(search): 커뮤니티 검색 테스트 추가 및 주석
ayoung-dev Dec 8, 2024
78990d0
test(search): 봉사활동 모집글 검색 테스트 추가 및 주석
ayoung-dev Dec 8, 2024
0cd0c2b
chore(search): 개행 추가
ayoung-dev Dec 9, 2024
9abb6ab
refactor(search): 기존 recruitBoard 검색 / elasicsearch 검색 분리
ayoung-dev Dec 9, 2024
fc492f4
test(search): 기존 recruitBoard 검색 / elasicsearch 검색 분리 테스트 수정
ayoung-dev Dec 9, 2024
fd51439
test(search): 불필요한 import 제거
ayoung-dev Dec 9, 2024
6856bed
test(search): elastic search uri 변경
ayoung-dev Dec 9, 2024
be7a3a3
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
ayoung-dev Dec 9, 2024
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ dependencies {
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3'

//elastic-search
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'

//test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/somemore/SomemoreApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class SomemoreApplication {

public static void main(String[] args) {
SpringApplication.run(SomemoreApplication.class, args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.somemore.community.dto.response.CommunityBoardDetailResponseDto;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import com.somemore.community.usecase.board.CommunityBoardQueryUseCase;
import com.somemore.global.common.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -20,6 +21,7 @@
public class CommunityBoardQueryApiController {

private final CommunityBoardQueryUseCase communityBoardQueryUseCase;
private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase;

@GetMapping("/community-boards")
@Operation(summary = "전체 커뮤니티 게시글 조회", description = "전체 커뮤니티 게시글 목록을 조회합니다.")
Expand All @@ -46,6 +48,19 @@ public ApiResponse<Page<CommunityBoardResponseDto>> getByWriterId(
);
}

@GetMapping("/community-boards/search")
@Operation(summary = "커뮤니티 게시글 키워드 검색", description = "키워드로 포함한 커뮤니티 게시글 목록을 조회합니다.")
public ApiResponse<Page<CommunityBoardResponseDto>> getCommunityBoardsBySearch(
String keyword,
Pageable pageable
) {
return ApiResponse.ok(
200,
communityBoardDocumentUseCase.getCommunityBoardBySearch(keyword, pageable.getPageNumber()),
"커뮤니티 게시글 검색 리스트 조회 성공"
);
}

@GetMapping("/community-board/{id}")
@Operation(summary = "커뮤니티 게시글 상세 조회", description = "커뮤니티 게시글의 상세 정보를 조회합니다.")
public ApiResponse<CommunityBoardDetailResponseDto> getById(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.somemore.community.domain;

import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Getter
@Document(indexName = "community_board")
public class CommunityBoardDocument {

@Id
private Long id;

@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String title;

@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String content;

@Builder
public CommunityBoardDocument(Long id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.somemore.community.repository.board;

import com.somemore.community.domain.CommunityBoardDocument;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface CommunityBoardDocumentRepository extends ElasticsearchRepository<CommunityBoardDocument, Long> {
List<CommunityBoardDocument> findAll();
@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}")
List<CommunityBoardDocument> findIdsByTitleOrContentContaining(String keyword);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

Expand All @@ -18,4 +19,9 @@ default boolean doesNotExistById(Long id) {
return !existsById(id);
}
void deleteAllInBatch();

Page<CommunityBoardView> findByCommunityBoardsContaining(String keyword, Pageable pageable);
void saveDocuments(List<CommunityBoard> communityBoards);
List<CommunityBoard> findAll();
void deleteDocument(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.domain.CommunityBoardDocument;
import com.somemore.community.repository.mapper.CommunityBoardView;
import com.somemore.community.domain.QCommunityBoard;
import com.somemore.volunteer.domain.QVolunteer;
Expand All @@ -14,6 +15,7 @@
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand All @@ -24,6 +26,7 @@ public class CommunityBoardRepositoryImpl implements CommunityBoardRepository {

private final JPAQueryFactory queryFactory;
private final CommunityBoardJpaRepository communityBoardJpaRepository;
private final CommunityBoardDocumentRepository documentRepository;

private static final QCommunityBoard communityBoard = QCommunityBoard.communityBoard;
private static final QVolunteer volunteer = QVolunteer.volunteer;
Expand Down Expand Up @@ -83,6 +86,52 @@ public boolean existsById(Long id) {
return communityBoardJpaRepository.existsByIdAndDeletedFalse(id);
}

@Override
public Page<CommunityBoardView> findByCommunityBoardsContaining(String keyword, Pageable pageable) {
List<CommunityBoardDocument> boardDocuments = getBoardDocuments(keyword);

List<Long> boardIds = boardDocuments.stream()
.map(CommunityBoardDocument::getId)
.toList();

List<CommunityBoardView> content = getCommunityBoardsQuery()
.where(communityBoard.id.in(boardIds)
.and(isNotDeleted()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

JPAQuery<Long> countQuery = queryFactory
.select(communityBoard.count())
.from(communityBoard)
.join(volunteer).on(communityBoard.writerId.eq(volunteer.id))
.where(communityBoard.id.in(boardIds)
.and(isNotDeleted()));

return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}

@Override
public void saveDocuments(List<CommunityBoard> communityBoards) {
List<CommunityBoardDocument> communityBoardDocuments = convertEntityToDocuments(communityBoards);
documentRepository.saveAll(communityBoardDocuments);
}

@Override
public void deleteDocument(Long id) {
documentRepository.deleteById(id);
}

@Override
public List<CommunityBoard> findAll() {
return communityBoardJpaRepository.findAll();
}

@Override
public void deleteAllInBatch() {
communityBoardJpaRepository.deleteAllInBatch();
}

private JPAQuery<CommunityBoardView> getCommunityBoardsQuery() {
return queryFactory
.select(Projections.constructor(CommunityBoardView.class,
Expand All @@ -93,15 +142,31 @@ private JPAQuery<CommunityBoardView> getCommunityBoardsQuery() {
.orderBy(communityBoard.createdAt.desc());
}


@Override
public void deleteAllInBatch() {
communityBoardJpaRepository.deleteAllInBatch();
private List<CommunityBoardDocument> convertEntityToDocuments(List<CommunityBoard> communityBoards) {
List<CommunityBoardDocument> communityBoardDocuments = new ArrayList<>();

for (CommunityBoard communityboard : communityBoards) {
CommunityBoardDocument document = CommunityBoardDocument.builder()
.id(communityboard.getId())
.title(communityboard.getTitle())
.content(communityboard.getContent())
.build();
communityBoardDocuments.add(document);
}
return communityBoardDocuments;
}

private BooleanExpression isNotDeleted() {
return communityBoard.deleted.eq(false);
}

private BooleanExpression isWriter(UUID writerId) {return communityBoard.writerId.eq(writerId); }

private List<CommunityBoardDocument> getBoardDocuments(String keyword) {

if (keyword == null || keyword.isEmpty()) {
return documentRepository.findAll();
}
return documentRepository.findIdsByTitleOrContentContaining(keyword);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.somemore.community.scheduler;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import com.somemore.community.usecase.board.CommunityBoardQueryUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class CommunityScheduler {

private final CommunityBoardQueryUseCase communityBoardQueryUseCase;
private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase;


@Scheduled(cron = "0 0 0 * * *")
public void updateCommunityBoardDocuments() {
List<CommunityBoard> communityBoards = communityBoardQueryUseCase.getAllCommunityBoards();
communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.somemore.community.service.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.mapper.CommunityBoardView;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Service
public class CommunityBoardDocumentService implements CommunityBoardDocumentUseCase {

private final CommunityBoardRepository communityBoardRepository;
private static final int PAGE_SIZE = 10;

@Transactional(readOnly = true)
@Override
public Page<CommunityBoardResponseDto> getCommunityBoardBySearch(String keyword, int page) {
Pageable pageable = PageRequest.of(page, PAGE_SIZE);
Page<CommunityBoardView> boards = communityBoardRepository.findByCommunityBoardsContaining(keyword, pageable);
return boards.map(CommunityBoardResponseDto::from);
}

@Transactional
@Override
public void saveCommunityBoardDocuments(List<CommunityBoard> communityBoards) {
communityBoardRepository.saveDocuments(communityBoards);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD;
Expand Down Expand Up @@ -46,4 +47,9 @@ public CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id) {
.orElseThrow(() -> new BadRequestException(NOT_EXISTS_COMMUNITY_BOARD.getMessage()));
return CommunityBoardDetailResponseDto.from(board);
}

@Override
public List<CommunityBoard> getAllCommunityBoards() {
return communityBoardRepository.findAll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.somemore.community.usecase.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import org.springframework.data.domain.Page;

import java.util.List;

public interface CommunityBoardDocumentUseCase {
Page<CommunityBoardResponseDto> getCommunityBoardBySearch(String keyword, int page);
void saveCommunityBoardDocuments(List<CommunityBoard> communityBoards);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.somemore.community.usecase.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardDetailResponseDto;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import org.springframework.data.domain.Page;

import java.util.List;
import java.util.UUID;

public interface CommunityBoardQueryUseCase {
Page<CommunityBoardResponseDto> getCommunityBoards(int page);
Page<CommunityBoardResponseDto> getCommunityBoardsByWriterId(UUID writerId, int page);
CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id);
List<CommunityBoard> getAllCommunityBoards();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.somemore.global.configure;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;

@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {
@Value("${elastic.search.uri}")
private String uri;
@Value("${elastic.search.username}")
private String username;
@Value("${elastic.search.password}")
private String password;

@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(uri)
.withBasicAuth(username, password)
.build();
}
}
Loading
Loading