diff --git a/build.gradle b/build.gradle index f9ff51d..963f995 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,11 @@ repositories { } dependencies { + //elasticsearch implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' + implementation 'io.github.cdimascio:java-dotenv:5.2.2' + + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -33,13 +37,16 @@ dependencies { implementation 'org.apache.poi:poi:5.2.3' implementation 'org.apache.poi:poi-ooxml:5.2.3' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' // Swagger + } tasks.named('test') { @@ -68,3 +75,4 @@ dependencies { annotationProcessor "jakarta.persistence:jakarta.persistence-api:3.1.0" annotationProcessor "jakarta.annotation:jakarta.annotation-api:2.1.1" } + diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/config/ElasticSearchConfig.java b/src/main/java/com/elasticsearch/elasticsearchengine/config/ElasticSearchConfig.java new file mode 100644 index 0000000..29d045c --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/config/ElasticSearchConfig.java @@ -0,0 +1,45 @@ +package com.elasticsearch.elasticsearchengine.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import io.github.cdimascio.dotenv.Dotenv; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.message.BasicHeader; +import org.elasticsearch.client.RestClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +@Configuration +public class ElasticSearchConfig { + + private final Dotenv dotenv = Dotenv.configure().directory("C:/git/elasticSearchEngine/docker-elk").load(); + + @Bean + public ElasticsearchClient elasticsearchClient() { + String username = "elastic"; + String password = dotenv.get("ELASTIC_PASSWORD"); + + RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)) + .setDefaultHeaders(new Header[]{ + new BasicHeader("Authorization", basicAuthHeader(username, password)) + }) + .build(); + + ElasticsearchTransport transport = new RestClientTransport( + restClient, new JacksonJsonpMapper()); + + return new ElasticsearchClient(transport); + } + + private String basicAuthHeader(String username, String password) { + String auth = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourController.java b/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourController.java index 0a5070e..cd597a0 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourController.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourController.java @@ -1,21 +1,20 @@ package com.elasticsearch.elasticsearchengine.controller; import com.elasticsearch.elasticsearchengine.common.CommonResponse; -import com.elasticsearch.elasticsearchengine.dto.ExcelSaveTourListResponseDto; -import com.elasticsearch.elasticsearchengine.dto.TourListResponseDto; +import com.elasticsearch.elasticsearchengine.dto.TourListRequestDto; import com.elasticsearch.elasticsearchengine.service.TourService; import com.elasticsearch.elasticsearchengine.vo.ExcelSaveTourListResponseVo; import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Arrays; import java.util.List; import static com.elasticsearch.elasticsearchengine.common.BaseResponseStatus.INTERNAL_SERVER_ERROR; @@ -38,8 +37,7 @@ public CommonResponse excelSaveTourList( ) MultipartFile file ) { try { - ExcelSaveTourListResponseDto responseDto = tourService.excelSaveTourList(file); - ExcelSaveTourListResponseVo responseVo = ExcelSaveTourListResponseVo.dtoToVo(responseDto); + ExcelSaveTourListResponseVo responseVo = tourService.excelSaveTourList(file); return CommonResponse.success("관광정보가 성공적으로 등록되었습니다.", responseVo); } catch (IOException e) { return CommonResponse.fail(INTERNAL_SERVER_ERROR, "파일 처리 중 오류가 발생했습니다."); @@ -49,32 +47,26 @@ public CommonResponse excelSaveTourList( @GetMapping(value = "/find") @Operation(summary = "관광 정보 조회", description = "페이지 번호, 관광 타입 번호, 시군구코드, 서브 카테고리, 태그를 기준으로 관광 정보를 조회합니다.") - @Parameters({ - @Parameter(name = "page", description = "페이지 번호, 기본 값 : 1"), - @Parameter(name = "contentTypeId", description = "관광 타입 번호"), - @Parameter(name = "sigunguCode", description = "시군구코드"), - @Parameter(name = "sideCategory", description = "서브 카테고리"), - @Parameter(name = "tags", description = "태그"), - }) public CommonResponse findTourList( - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "contentTypeId") String contentTypeId, - @RequestParam(name = "sigunguCode", required = false) String sigunguCode, - @RequestParam(name = "sideCategory", required = false) String sideCategory, - @RequestParam(name = "tags", required = false) List tags + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "검색 조건 및 페이징 정보") + TourListRequestDto tourListRequestDto ) { - Page TourListPage = tourService.findByMultiCode(page, contentTypeId, sigunguCode, sideCategory, tags); + Pageable pageable = PageRequest.of(tourListRequestDto.getPage(), tourListRequestDto.getSize()); - if (TourListPage.isEmpty()) { - return CommonResponse.success("관광 정보", TourListResponseVo.builder() - .totalList(0) - .currentPage(page) - .totalPages(0) - .tourListDto(null) - .build()); - } + List tagList = tourListRequestDto.getTags() != null + ? Arrays.stream(tourListRequestDto.getTags().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList() + : List.of(); + + TourListResponseVo tourListResponseVo = tourService.findByMultiCode( + tourListRequestDto.getContentTypeId(), + tourListRequestDto.getSigunguCode(), + tourListRequestDto.getCategory(), + tagList, + pageable); - return CommonResponse.success("관광 정보", - TourListResponseVo.dtoToVo(TourListPage.getContent().get(0))); + return CommonResponse.success("관광 정보", tourListResponseVo); } } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourElasticSearchController.java b/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourElasticSearchController.java new file mode 100644 index 0000000..ae86ab2 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/controller/TourElasticSearchController.java @@ -0,0 +1,64 @@ +package com.elasticsearch.elasticsearchengine.controller; + +import com.elasticsearch.elasticsearchengine.common.CommonResponse; +import com.elasticsearch.elasticsearchengine.dto.TourListRequestDto; +import com.elasticsearch.elasticsearchengine.service.TourElasticSearchService; +import com.elasticsearch.elasticsearchengine.vo.TourListDetailResponseVo; +import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("tour_E") +@Tag(name = "tour_elasticSearch_controller", description = "관광정보관리시스템 ver.elasticSearch") +public class TourElasticSearchController { + + private final TourElasticSearchService tourElasticSearchService; + + @GetMapping(value = "/find") + @Operation(summary = "관광 정보 조회", description = "페이지 번호, 관광 타입 번호, 시군구코드, 서브 카테고리, 태그를 기준으로 관광 정보를 조회합니다.") + public CommonResponse searchTourWithPasing( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "검색 조건 및 페이징 정보") + TourListRequestDto tourListRequestDto + ) { + Pageable pageable = PageRequest.of(tourListRequestDto.getPage(), tourListRequestDto.getSize()); + + List tagList = tourListRequestDto.getTags() != null + ? Arrays.stream(tourListRequestDto.getTags().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList() + : List.of(); + + TourListResponseVo tourListResponseVo = tourElasticSearchService.searchTourWithPasing( + tourListRequestDto.getContentTypeId(), + tourListRequestDto.getTitle(), + tourListRequestDto.getSigunguCode(), + tourListRequestDto.getCategory(), + tagList, + tourListRequestDto.getSort(), + pageable); + + return CommonResponse.success("관광 정보", tourListResponseVo); + } + + @GetMapping(value = "/find/detail") + @Operation(summary = "관광 정보 상세 조회", description = "콘텐츠 고유 번호를 기준으로 관광 정보를 상세 조회합니다.") + public CommonResponse findByContentId( + @Parameter(description = "콘텐츠 고유 번호") + int contentId + ) { + TourListDetailResponseVo tourListDetailResponseVo = tourElasticSearchService.findByContentId(contentId); + + return CommonResponse.success("관광 정보", tourListDetailResponseVo); + } +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/domain/TourAttractionDocument.java b/src/main/java/com/elasticsearch/elasticsearchengine/domain/TourAttractionDocument.java new file mode 100644 index 0000000..a05cd26 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/domain/TourAttractionDocument.java @@ -0,0 +1,74 @@ +package com.elasticsearch.elasticsearchengine.domain; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.time.LocalDateTime; +import java.util.List; + +@Document(indexName = "tourist_attraction_index") +@Data +public class TourAttractionDocument { + + @Id + @Field(name = "content_id", type = FieldType.Integer) + private int contentId; + + @Field(name = "content_type_id") + private String contentTypeId; + + @Field(name = "title") + private String title; + + @Field(name = "sigungu_code") + private String sigunguCode; + + private String category; + + private List tags; + + @Field(name = "title_sort", type = FieldType.Keyword) + private String title_sort; + + private String addr; + + private String thumbnail; + + @Field(name = "created_time") + private LocalDateTime createdTime; + + @Field(name = "modified_time") + private LocalDateTime modifiedTime; + + private String overview; + + private String homepage; + + private String infocenter; + + private String opendate; + + private String restdate; + + private String expguide; + + private String expagerange; + + private String accomcount; + + private String useseason; + + private String usetime; + + private String parking; + + private double mapx; + + private double mapy; + + @Field(name = "area_code") + private String areaCode; +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/domain/Tour.java b/src/main/java/com/elasticsearch/elasticsearchengine/domain/TouristAttreaction.java similarity index 56% rename from src/main/java/com/elasticsearch/elasticsearchengine/domain/Tour.java rename to src/main/java/com/elasticsearch/elasticsearchengine/domain/TouristAttreaction.java index 89633df..f0705c8 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/domain/Tour.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/domain/TouristAttreaction.java @@ -8,8 +8,8 @@ @Data @AllArgsConstructor @NoArgsConstructor -@Entity(name = "tour") -public class Tour { +@Entity(name = "tourist_attraction") +public class TouristAttreaction { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -22,27 +22,49 @@ public class Tour { private String title; + private String sigunguCode; + + private String category; + + private String tags; + private String addr; - private String zipCode; + private String thumbnail; - private String areaCode; + private LocalDate createdTime; - private String sigunguCode; + private LocalDate modifiedTime; - private String category; + @Column(columnDefinition = "text") + private String overview; - private String sideCategory; + @Column(columnDefinition = "text") + private String homepage; - private String tags; + private String infocenter; - private String thumbnail; + private String opendate; + + private String restdate; + + @Column(columnDefinition = "text") + private String expguide; + + private String expagerange; + + private String accomcount; + + private String useseason; + + @Column(columnDefinition = "text") + private String usetime; + + private String parking; private double mapx; private double mapy; - private LocalDate createdTime; - - private LocalDate modifiedTime; + private String areaCode; } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/dto/ExcelSaveTourListResponseDto.java b/src/main/java/com/elasticsearch/elasticsearchengine/dto/ExcelSaveTourListResponseDto.java deleted file mode 100644 index a8bd6e4..0000000 --- a/src/main/java/com/elasticsearch/elasticsearchengine/dto/ExcelSaveTourListResponseDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.elasticsearch.elasticsearchengine.dto; - -import lombok.Builder; -import lombok.Data; - -import java.util.List; - -@Builder -@Data -public class ExcelSaveTourListResponseDto { - - private int successCount; - private int failedCount; - private List errors; -} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListDto.java b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListDto.java index 002677d..ec70ef8 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListDto.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListDto.java @@ -5,8 +5,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; - @Data @Builder @AllArgsConstructor @@ -16,4 +14,4 @@ public class TourListDto { private String title; private String thumbnail; private String tags; -} +} \ No newline at end of file diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListRequestDto.java b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListRequestDto.java new file mode 100644 index 0000000..987ceb4 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListRequestDto.java @@ -0,0 +1,15 @@ +package com.elasticsearch.elasticsearchengine.dto; + +import lombok.Data; + +@Data +public class TourListRequestDto { + private String contentTypeId = "12"; + private String title; + private String sigunguCode; + private String category; + private String tags; + private int sort = 0; + private int page = 1; + private int size = 12; +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListResponseDto.java b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListResponseDto.java deleted file mode 100644 index d940958..0000000 --- a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourListResponseDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.elasticsearch.elasticsearchengine.dto; - -import lombok.Builder; -import lombok.Data; - -import java.util.List; - -@Data -@Builder -public class TourListResponseDto { - private int totalList; - private int currentPage; - private int totalPages; - private List tourListDto; -} \ No newline at end of file diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourSaveRequestDto.java b/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourSaveRequestDto.java deleted file mode 100644 index 2a6ff4a..0000000 --- a/src/main/java/com/elasticsearch/elasticsearchengine/dto/TourSaveRequestDto.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.elasticsearch.elasticsearchengine.dto; - -import lombok.Data; - -import java.time.LocalDate; - -@Data -public class TourSaveRequestDto { - - private int contentId; - - private String contentTypeId; - - private String title; - - private String addr; - - private String zipCode; - - private String areaCode; - - private String sigunguCode; - - private String category; - - private String sideCategory; - - private String tags; - - private String thumbnail; - - private double mapx; - - private double mapy; - - private LocalDate createdTime; - - private LocalDate modifiedTime; -} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourAttractionRepository.java b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourAttractionRepository.java new file mode 100644 index 0000000..b3de5a3 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourAttractionRepository.java @@ -0,0 +1,12 @@ +package com.elasticsearch.elasticsearchengine.repository; + +import com.elasticsearch.elasticsearchengine.domain.TourAttractionDocument; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.Optional; + +public interface TourAttractionRepository extends ElasticsearchRepository { + + Optional findByContentId(int contentId); + +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepository.java b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepository.java index d8004e7..7e39f49 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepository.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepository.java @@ -1,17 +1,12 @@ package com.elasticsearch.elasticsearchengine.repository; -import com.elasticsearch.elasticsearchengine.domain.Tour; import com.elasticsearch.elasticsearchengine.dto.TourListDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.query.Param; import java.util.List; public interface TourQueryRepository { - Page findByMultiCode(String contentTypeId, String sigunguCode, String sideCategory, List tags, Pageable pageable); - - //자연용 - Page findMultiCodeNature(String contentTypeId, String category, String sigunguCode, String sideCategory, List tags, Pageable pageable); + Page findByMultiCode(String contentTypeId, String sigunguCode, String category, List tags, Pageable pageable); } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepositoryImpl.java b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepositoryImpl.java index 1a00a6b..61502ee 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepositoryImpl.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourQueryRepositoryImpl.java @@ -1,17 +1,14 @@ package com.elasticsearch.elasticsearchengine.repository; -import com.elasticsearch.elasticsearchengine.domain.QTour; -import com.elasticsearch.elasticsearchengine.domain.Tour; +import com.elasticsearch.elasticsearchengine.domain.QTouristAttreaction; import com.elasticsearch.elasticsearchengine.dto.TourListDto; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.expression.spel.ast.Projection; import java.util.List; @@ -22,25 +19,25 @@ public class TourQueryRepositoryImpl implements TourQueryRepository { private final JPAQueryFactory queryFactory; @Override - public Page findByMultiCode(String contentTypeId, String sigunguCode, String sideCategory, List tags, Pageable pageable) { - QTour tour = QTour.tour; + public Page findByMultiCode(String contentTypeId, String sigunguCode, String category, List tags, Pageable pageable) { + QTouristAttreaction touristAttreaction = QTouristAttreaction.touristAttreaction; BooleanBuilder builder = new BooleanBuilder(); - builder.and(tour.contentTypeId.eq(contentTypeId)); + builder.and(touristAttreaction.contentTypeId.eq(contentTypeId)); if (sigunguCode != null && !sigunguCode.isBlank()) { - builder.and(tour.sigunguCode.eq(sigunguCode)); + builder.and(touristAttreaction.sigunguCode.eq(sigunguCode)); } - if (sideCategory != null && !sideCategory.isBlank()) { - builder.and(tour.sideCategory.eq(sideCategory)); + if (category != null && !category.isBlank()) { + builder.and(touristAttreaction.category.eq(category)); } if (tags != null && !tags.isEmpty()) { BooleanBuilder tagBuilder = new BooleanBuilder(); for (String tag : tags) { - tagBuilder.or(tour.tags.contains(tag)); // tags LIKE %tag% + tagBuilder.or(touristAttreaction.tags.contains(tag)); // tags LIKE %tag% } builder.and(tagBuilder); } @@ -48,68 +45,20 @@ public Page findByMultiCode(String contentTypeId, String sigunguCod List content = queryFactory .select(Projections.constructor( TourListDto.class, - tour.contentId, - tour.title, - tour.thumbnail, - tour.tags + touristAttreaction.contentId, + touristAttreaction.title, + touristAttreaction.thumbnail, + touristAttreaction.tags )) - .from(tour) + .from(touristAttreaction) .where(builder) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); Long total = queryFactory - .select(tour.count()) - .from(tour) - .where(builder) - .fetchOne(); - - return new PageImpl<>(content, pageable, total == null ? 0 : total); - } - - @Override - public Page findMultiCodeNature(String contentTypeId, String category, String sigunguCode, String sideCategory, List tags, Pageable pageable) { - QTour tour = QTour.tour; - - BooleanBuilder builder = new BooleanBuilder(); - - builder.and(tour.contentTypeId.eq(contentTypeId)); - builder.and(tour.category.eq(category)); - - if (sigunguCode != null && !sigunguCode.isBlank()) { - builder.and(tour.sigunguCode.eq(sigunguCode)); - } - - if (sideCategory != null && !sideCategory.isBlank()) { - builder.and(tour.sideCategory.eq(sideCategory)); - } - - if (tags != null && !tags.isEmpty()) { - BooleanBuilder tagBuilder = new BooleanBuilder(); - for (String tag : tags) { - tagBuilder.or(tour.tags.contains(tag)); // tags LIKE %tag% - } - builder.and(tagBuilder); - } - - List content = queryFactory - .select(Projections.constructor( - TourListDto.class, - tour.contentId.stringValue(), - tour.title, - tour.thumbnail, - Expressions.stringTemplate("function('string_to_array', {0}, ',')", tour.tags) - )) - .from(tour) - .where(builder) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - - Long total = queryFactory - .select(tour.count()) - .from(tour) + .select(touristAttreaction.count()) + .from(touristAttreaction) .where(builder) .fetchOne(); diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourRepository.java b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourRepository.java index f63d76b..2f42390 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourRepository.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/repository/TourRepository.java @@ -1,18 +1,13 @@ package com.elasticsearch.elasticsearchengine.repository; -import com.elasticsearch.elasticsearchengine.domain.Tour; +import com.elasticsearch.elasticsearchengine.domain.TouristAttreaction; +import com.elasticsearch.elasticsearchengine.dto.TourListDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -public interface TourRepository extends JpaRepository , TourQueryRepository { - - Page findByContentTypeId(String contentTypeId, Pageable pageable); - - //자연용 - Page findByContentTypeIdAndCategory(String contentTypeId, String category, Pageable pageable); - - +public interface TourRepository extends JpaRepository , TourQueryRepository { + Page findByContentTypeId(String contentTypeId, Pageable pageable); } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchService.java b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchService.java new file mode 100644 index 0000000..e3ff358 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchService.java @@ -0,0 +1,12 @@ +package com.elasticsearch.elasticsearchengine.service; + +import com.elasticsearch.elasticsearchengine.vo.TourListDetailResponseVo; +import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface TourElasticSearchService { + public TourListResponseVo searchTourWithPasing(String contentTypeId, String title, String sigunguCode, String category, List tags, int sort, Pageable pageable); + public TourListDetailResponseVo findByContentId(int contentId); +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchServiceImpl.java b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchServiceImpl.java new file mode 100644 index 0000000..d16bcb0 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourElasticSearchServiceImpl.java @@ -0,0 +1,113 @@ +package com.elasticsearch.elasticsearchengine.service; + +import com.elasticsearch.elasticsearchengine.domain.TourAttractionDocument; +import com.elasticsearch.elasticsearchengine.dto.TourListDto; +import com.elasticsearch.elasticsearchengine.repository.TourAttractionRepository; +import com.elasticsearch.elasticsearchengine.vo.TourListDetailResponseVo; +import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; +import lombok.AllArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.Criteria; +import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; + +@Service +@AllArgsConstructor +public class TourElasticSearchServiceImpl implements TourElasticSearchService { + + private final ElasticsearchOperations elasticsearchOperations; + private final TourAttractionRepository tourAttractionRepository; + + @Override + public TourListResponseVo searchTourWithPasing(String contentTypeId, String title, String sigunguCode, String category, List tags, int sort, Pageable pageable) { + + Criteria criteria = new Criteria("content_type_id").is(contentTypeId); + + if (title != null && !title.isEmpty()) { + criteria = criteria.and(new Criteria("title").contains(title)); + } + if (sigunguCode != null && !sigunguCode.isEmpty()) { + criteria = criteria.and(new Criteria("sigungu_code").is(sigunguCode)); + } + if (category != null && !category.isEmpty()) { + criteria = criteria.and(new Criteria("category").is(category)); + } + if (tags != null && !tags.isEmpty()) { + // 하나라도 포함되는 조건 + Criteria tagsCriteria = new Criteria("tags"); + for (String tag : tags) { + tagsCriteria = tagsCriteria.or(new Criteria("tags").contains(tag)); + } + criteria = criteria.and(tagsCriteria); + } + + CriteriaQuery query = new CriteriaQuery(criteria, pageable); + + // 정렬 적용 + if (sort == 1) { + query.addSort(Sort.by(Sort.Order.asc("title_sort"))); + } else { + query.addSort(Sort.by(Sort.Order.desc("created_time"))); + } + + SearchHits hits = elasticsearchOperations.search(query, TourAttractionDocument.class); + + int totalCount = (int) hits.getTotalHits(); + int currentPage = pageable.getPageNumber(); + int totalPages = (int) Math.ceil((double) totalCount / pageable.getPageSize()); + + List tourList = hits.getSearchHits().stream() + .map(SearchHit::getContent) + .map(doc -> TourListDto.builder() + .contentId(doc.getContentId()) + .title(doc.getTitle()) + .thumbnail(doc.getThumbnail()) + .tags(String.join(",", doc.getTags())) + .build()) + .toList(); + + return TourListResponseVo.builder() + .totalCount(totalCount) + .currentPage(currentPage) + .totalPages(totalPages) + .tourListDto(tourList) + .build(); + } + + @Override + public TourListDetailResponseVo findByContentId(int contentId) { + TourAttractionDocument TourDetail = tourAttractionRepository.findByContentId(contentId).orElseThrow(RuntimeException::new); + + return TourListDetailResponseVo.builder() + .contentId(TourDetail.getContentId()) + .title(TourDetail.getTitle()) + .tags(String.join(",", TourDetail.getTags())) + .addr(TourDetail.getAddr()) + .thumbnail(TourDetail.getThumbnail()) + .createdTime(LocalDate.from(TourDetail.getCreatedTime())) + .modifiedTime(LocalDate.from(TourDetail.getModifiedTime())) + .overview(TourDetail.getOverview()) + .homepage(TourDetail.getHomepage()) + .infocenter(TourDetail.getInfocenter()) + .opendate(TourDetail.getOpendate()) + .restdate(TourDetail.getRestdate()) + .expguide(TourDetail.getExpguide()) + .expagerange(TourDetail.getExpagerange()) + .accomcount(TourDetail.getAccomcount()) + .useseason(TourDetail.getUseseason()) + .usetime(TourDetail.getUsetime()) + .parking(TourDetail.getParking()) + .mapx(TourDetail.getMapx()) + .mapy(TourDetail.getMapy()) + .build(); + } + + +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourService.java b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourService.java index d970429..a6b1361 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourService.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourService.java @@ -1,8 +1,7 @@ package com.elasticsearch.elasticsearchengine.service; -import com.elasticsearch.elasticsearchengine.dto.ExcelSaveTourListResponseDto; -import com.elasticsearch.elasticsearchengine.dto.TourListResponseDto; -import org.springframework.data.domain.Page; +import com.elasticsearch.elasticsearchengine.vo.ExcelSaveTourListResponseVo; +import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; import org.springframework.data.domain.Pageable; import org.springframework.web.multipart.MultipartFile; @@ -11,7 +10,7 @@ public interface TourService { - public ExcelSaveTourListResponseDto excelSaveTourList(MultipartFile file) throws IOException; - public Page findBycontentTypeId(int page, String contentTypeId); - public Page findByMultiCode(int page, String contentTypeId, String sigunguCode, String sideCategory, List tags); + public ExcelSaveTourListResponseVo excelSaveTourList(MultipartFile file) throws IOException; + public TourListResponseVo findBycontentTypeId(String contentTypeId, Pageable pageable); + public TourListResponseVo findByMultiCode(String contentTypeId, String sigunguCode, String category, List tags, Pageable pageable); } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourServiceImpl.java b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourServiceImpl.java index db62b4c..a7205c7 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/service/TourServiceImpl.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/service/TourServiceImpl.java @@ -1,16 +1,13 @@ package com.elasticsearch.elasticsearchengine.service; -import com.elasticsearch.elasticsearchengine.domain.Tour; -import com.elasticsearch.elasticsearchengine.dto.ExcelSaveTourListResponseDto; +import com.elasticsearch.elasticsearchengine.domain.TouristAttreaction; import com.elasticsearch.elasticsearchengine.dto.TourListDto; -import com.elasticsearch.elasticsearchengine.dto.TourListResponseDto; import com.elasticsearch.elasticsearchengine.repository.TourRepository; +import com.elasticsearch.elasticsearchengine.vo.ExcelSaveTourListResponseVo; +import com.elasticsearch.elasticsearchengine.vo.TourListResponseVo; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -33,11 +30,11 @@ public class TourServiceImpl implements TourService { @Override @Transactional - public ExcelSaveTourListResponseDto excelSaveTourList(MultipartFile file) throws IOException { + public ExcelSaveTourListResponseVo excelSaveTourList(MultipartFile file) throws IOException { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); - List TourToSave = new ArrayList<>(); + List TouristAttreactionToSave = new ArrayList<>(); List errors = new ArrayList<>(); int successCount = 0; int failedCount = 0; @@ -51,44 +48,62 @@ public ExcelSaveTourListResponseDto excelSaveTourList(MultipartFile file) throws String contentId = getCellValue(row.getCell(0)); String contentTypeId = getCellValue(row.getCell(1)); String title = getCellValue(row.getCell(2)); - String addr = getCellValue(row.getCell(3)); - String zipCode = getCellValue(row.getCell(4)); - String areaCode = getCellValue(row.getCell(5)); - String sigunguCode = getCellValue(row.getCell(6)); - String category = getCellValue(row.getCell(7)); - String sideCategory = getCellValue(row.getCell(8)); - String tags = getCellValue(row.getCell(9)); - String thumbnail = getCellValue(row.getCell(10)); - String mapx = getCellValue(row.getCell(11)); - String mapy = getCellValue(row.getCell(12)); - String createdTime = getCellValue(row.getCell(13)); - String modifiedTime = getCellValue(row.getCell(14)); - - TourToSave.add(Tour.builder() + String sigunguCode = getCellValue(row.getCell(3)); + String category = getCellValue(row.getCell(4)); + String tags = getCellValue(row.getCell(5)); + String addr = getCellValue(row.getCell(6)); + String thumbnail = getCellValue(row.getCell(7)); + String createdTime = getCellValue(row.getCell(8)); + String modifiedTime = getCellValue(row.getCell(9)); + String overview = getCellValue(row.getCell(10)); + String homepage = getCellValue(row.getCell(11)); + String infocenter = getCellValue(row.getCell(12)); + String opendate = getCellValue(row.getCell(13)); + String restdate = getCellValue(row.getCell(14)); + String expguide = getCellValue(row.getCell(15)); + String expagerange = getCellValue(row.getCell(16)); + String accomcount = getCellValue(row.getCell(17)); + String useseason = getCellValue(row.getCell(18)); + String usetime = getCellValue(row.getCell(19)); + String parking = getCellValue(row.getCell(20)); + String mapx = getCellValue(row.getCell(21)); + String mapy = getCellValue(row.getCell(22)); + String areaCode = getCellValue(row.getCell(23)); + + TouristAttreactionToSave.add(TouristAttreaction.builder() .contentId(Integer.parseInt(contentId)) .contentTypeId(contentTypeId) .title(title) - .addr(addr) - .zipCode(zipCode) - .areaCode(areaCode) .sigunguCode(sigunguCode) .category(category) - .sideCategory(sideCategory) .tags(tags) + .addr(addr) .thumbnail(thumbnail) - .mapx(Double.parseDouble(mapx)) - .mapy(Double.parseDouble(mapy)) .createdTime(LocalDate.parse(createdTime, formatter)) .modifiedTime(LocalDate.parse(modifiedTime, formatter)) + .overview(overview) + .homepage(homepage) + .infocenter(infocenter) + .opendate(opendate) + .restdate(restdate) + .expguide(expguide) + .expagerange(expagerange) + .accomcount(accomcount) + .useseason(useseason) + .usetime(usetime) + .parking(parking) + .mapx(Double.parseDouble(mapx)) + .mapy(Double.parseDouble(mapy)) + .areaCode(areaCode) .build() ); successCount++; } } - tourRepository.saveAll(TourToSave); + tourRepository.saveAll(TouristAttreactionToSave); - return ExcelSaveTourListResponseDto.builder() + return ExcelSaveTourListResponseVo.builder() .successCount(successCount) .failedCount(failedCount) .errors(errors) @@ -106,41 +121,26 @@ private String getCellValue(Cell cell) { } @Override - public Page findBycontentTypeId(int page, String contentTypeId) { - PageRequest pageable = PageRequest.of(page, 12); - Page TourPage; - - TourPage = tourRepository.findByContentTypeId(contentTypeId, pageable); - - if (TourPage.isEmpty()) { - return Page.empty(); - } - - return TourPage.map(Tour -> TourListResponseDto.builder() - .totalList((int)TourPage.getTotalElements()) - .currentPage(TourPage.getNumber() + 1) - .totalPages(TourPage.getTotalPages()) - .build()); + public TourListResponseVo findBycontentTypeId(String contentTypeId, Pageable pageable) { + Page TourListPage = tourRepository.findByContentTypeId(contentTypeId, pageable); + + return TourListResponseVo.builder() + .totalCount((int) TourListPage.getTotalElements()) + .currentPage(TourListPage.getNumber() + 1) + .totalPages(TourListPage.getTotalPages()) + .tourListDto(TourListPage.toList()) + .build(); } @Override - public Page findByMultiCode(int page, String contentTypeId, String sigunguCode, String sideCategory, List tags) { - PageRequest pageable = PageRequest.of(page, 12); - Page TourPage; - - TourPage = tourRepository.findByMultiCode(contentTypeId, sigunguCode, sideCategory ,tags ,pageable); - - if (TourPage.isEmpty()) { - return Page.empty(); - } - - TourListResponseDto responseDto = TourListResponseDto.builder() - .totalList((int) TourPage.getTotalElements()) - .currentPage(TourPage.getNumber() + 1) - .totalPages(TourPage.getTotalPages()) - .tourListDto(TourPage.toList()) + public TourListResponseVo findByMultiCode(String contentTypeId, String sigunguCode, String category, List tags, Pageable pageable) { + Page TourListPage = tourRepository.findByMultiCode(contentTypeId, sigunguCode, category ,tags ,pageable); + + return TourListResponseVo.builder() + .totalCount((int) TourListPage.getTotalElements()) + .currentPage(TourListPage.getNumber() + 1) + .totalPages(TourListPage.getTotalPages()) + .tourListDto(TourListPage.toList()) .build(); - - return new PageImpl<>(List.of(responseDto), pageable, 1); } } \ No newline at end of file diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/vo/ExcelSaveTourListResponseVo.java b/src/main/java/com/elasticsearch/elasticsearchengine/vo/ExcelSaveTourListResponseVo.java index 7d88263..9d15ce8 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/vo/ExcelSaveTourListResponseVo.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/vo/ExcelSaveTourListResponseVo.java @@ -1,6 +1,5 @@ package com.elasticsearch.elasticsearchengine.vo; -import com.elasticsearch.elasticsearchengine.dto.ExcelSaveTourListResponseDto; import lombok.Builder; import lombok.Data; @@ -11,14 +10,6 @@ public class ExcelSaveTourListResponseVo { private int successCount; - private int errorCount; + private int failedCount; private List errors; - - public static ExcelSaveTourListResponseVo dtoToVo(ExcelSaveTourListResponseDto excelSaveTourListResponseDto) { - return ExcelSaveTourListResponseVo.builder() - .successCount(excelSaveTourListResponseDto.getSuccessCount()) - .errorCount(excelSaveTourListResponseDto.getFailedCount()) - .errors(excelSaveTourListResponseDto.getErrors()) - .build(); - } } diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListDetailResponseVo.java b/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListDetailResponseVo.java new file mode 100644 index 0000000..a88d8d8 --- /dev/null +++ b/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListDetailResponseVo.java @@ -0,0 +1,31 @@ +package com.elasticsearch.elasticsearchengine.vo; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; + +@Data +@Builder +public class TourListDetailResponseVo { + private int contentId; + private String title; + private String tags; + private String addr; + private String thumbnail; + private LocalDate createdTime; + private LocalDate modifiedTime; + private String overview; + private String homepage; + private String infocenter; + private String opendate; + private String restdate; + private String expguide; + private String expagerange; + private String accomcount; + private String useseason; + private String usetime; + private String parking; + private double mapx; + private double mapy; +} diff --git a/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListResponseVo.java b/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListResponseVo.java index d4c0375..f05263e 100644 --- a/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListResponseVo.java +++ b/src/main/java/com/elasticsearch/elasticsearchengine/vo/TourListResponseVo.java @@ -1,7 +1,6 @@ package com.elasticsearch.elasticsearchengine.vo; import com.elasticsearch.elasticsearchengine.dto.TourListDto; -import com.elasticsearch.elasticsearchengine.dto.TourListResponseDto; import lombok.Builder; import lombok.Data; @@ -10,17 +9,8 @@ @Data @Builder public class TourListResponseVo { - private int totalList; + private int totalCount; private int currentPage; private int totalPages; private List tourListDto; - - public static TourListResponseVo dtoToVo(TourListResponseDto tourListResponseDto) { - return TourListResponseVo.builder() - .totalList(tourListResponseDto.getTotalList()) - .currentPage(tourListResponseDto.getCurrentPage()) - .totalPages(tourListResponseDto.getTotalPages()) - .tourListDto(tourListResponseDto.getTourListDto()) - .build(); - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 42246df..92a6022 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,4 @@ spring.application.name=elasticSearchEngine -<<<<<<< HEAD -======= #postgresql spring.datasource.url=jdbc:postgresql://localhost:5432/postgres @@ -11,4 +9,3 @@ spring.datasource.driver-class-name=org.postgresql.Driver #jpa spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect ->>>>>>> 7ffefd2 (feat: 관광 정보 엑셀 저장 기능 API 및 페이징, 검색 조회 기능 API) diff --git a/tourist_attreaction_total_info.xlsx b/tourist_attreaction_total_info.xlsx new file mode 100644 index 0000000..2d851d9 Binary files /dev/null and b/tourist_attreaction_total_info.xlsx differ