From 374d8fa9663558e8dce6cbd25c2138126445d8d1 Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 16 Oct 2025 02:26:46 +0900 Subject: [PATCH 01/32] =?UTF-8?q?feat=20:=20=ED=95=84=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=20=EC=B6=94=ED=9B=84=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EA=B0=80=EB=8A=A5=EC=84=B1=20?= =?UTF-8?q?=EC=9E=88=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/members/enums/LoginType.java | 1 - .../domain/search/enums/filters/AgeGroup.java | 13 ++++++----- .../domain/search/enums/filters/Children.java | 6 ++++- .../search/enums/filters/FilterKey.java | 1 + .../domain/search/enums/filters/Gender.java | 9 ++++---- .../search/enums/filters/MartialStatus.java | 8 ++++++- .../search/enums/filters/Occupation.java | 11 +++++++++- .../search/enums/filters/Residence.java | 21 +++++++++++++++++- .../search/enums/filters/Respondent.java | 22 +++++++++++++++++++ 9 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/search/enums/filters/Respondent.java diff --git a/src/main/java/DiffLens/back_end/domain/members/enums/LoginType.java b/src/main/java/DiffLens/back_end/domain/members/enums/LoginType.java index 6fa58f8..9b8d312 100644 --- a/src/main/java/DiffLens/back_end/domain/members/enums/LoginType.java +++ b/src/main/java/DiffLens/back_end/domain/members/enums/LoginType.java @@ -7,7 +7,6 @@ @RequiredArgsConstructor public enum LoginType { - GENERAL("로컬", "LOCAL" ), GOOGLE("구글", "GOOGLE" ), // KAKAO, diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/AgeGroup.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/AgeGroup.java index 84dde6f..091a408 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/AgeGroup.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/AgeGroup.java @@ -7,14 +7,15 @@ @AllArgsConstructor public enum AgeGroup{ - TWENTY(1L, "20-29"), - THIRTY(2L, "30-39"), - FORTY(3L, "40-49"), - FIFTY(4L, "50-59"), - SIXTY_PLUS(5L, "60-69") + TWENTY(1L, "20-29세","20대"), + THIRTY(2L, "30-39세", "30대"), + FORTY(3L, "40-49세","40대"), + FIFTY(4L, "50-59세","50대"), + SIXTY_PLUS(5L, "60세 이상", "60대 이상"), ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값 } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java index e7dc918..f4a91fb 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java @@ -3,13 +3,17 @@ import lombok.AllArgsConstructor; import lombok.Getter; +// 자녀유무 @Getter @AllArgsConstructor public enum Children { + EXIST(1L, "있음", "true"), + NOT_EXIST(2L, "없음", "false") ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값 } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java index e50bcde..ec30795 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.List; +// 필터 모음 @Getter @AllArgsConstructor public enum FilterKey { diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java index e4103a2..99bd583 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java @@ -7,13 +7,14 @@ @AllArgsConstructor public enum Gender { - MALE(1L, "남자"), - FEMALE(2L, "여자"), - NONE(3L, "알수없음") + MALE(1L, "남성", "남성"), + FEMALE(2L, "여성", "여성"), + NONE(3L, "알수없음", "알수없음") ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값 } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java index 7473491..1840a14 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java @@ -7,9 +7,15 @@ @AllArgsConstructor public enum MartialStatus { + NOT_MARRIED(1L, "미혼", "미혼"), + MARRIED(2L, "기혼", "기혼"), + DIVORCE(3L, "이혼", "이혼"), + DEATH(4L, "사별", "사별"), + ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값rivate final String value; } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java index d55105b..add146c 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java @@ -3,13 +3,22 @@ import lombok.AllArgsConstructor; import lombok.Getter; +// 직업 @Getter @AllArgsConstructor public enum Occupation { + STUDENT(1L, "학생", "학생"), + BUSINESS(1L, "직장인", "직장인"), + CEO(1L, "자영업", "자영업"), // 자영업자 + JUBU(1L, "주부", "주부"), // 주부 + BAEKSU(1L, "무직", "무직"), // 무직 + ETC(1L, "기타", "기타"), + ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값 } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Residence.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Residence.java index 07281ed..07571f6 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Residence.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Residence.java @@ -7,9 +7,28 @@ @AllArgsConstructor public enum Residence { + SEOUL(1L, "서울", "서울"), + GYEONGGI(2L, "제주", "제주"), + INCHEON(3L, "인천", "인천"), + BUSAN(4L, "부산", "부산"), + DAEGU(5L, "대구", "대구"), + GWANGJU(6L, "광주", "광주"), + DAEJEON(7L, "대전", "대전"), + ULSAN(8L, "울산", "울산"), + SEJONG(9L, "세종", "세종"), + GANGWON(10L, "강원", "강원"), + CHUNG_NORTH(11L, "충북", "충북"), + CHUNG_SOUTH(12L, "충남", "충남"), + JUN_NORTH(13L, "전북", "전북"), + JUN_SOUTH(14L, "전남", "전남"), + GYEONG_NORTH(15L, "경북", "경북"), + GYEONG_SOUTH(16L, "경남", "경남"), + JEJU(17L, "제주", "제주"), + ; private final Long id; // 내부 시드 ID ( 상대적 ) - private final String value; + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값 } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Respondent.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Respondent.java new file mode 100644 index 0000000..3de3c0b --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Respondent.java @@ -0,0 +1,22 @@ +package DiffLens.back_end.domain.search.enums.filters; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Respondent { + + T5(1L, "50명", "50"), + H1(2L, "100명", "100"), + H3(3L, "300명", "300"), + H5(3L, "500명", "500"), + TH1(3L, "1000명", "1000"), + + ; + + private final Long id; // 내부 시드 ID ( 상대적 ) + private final String displayValue; // 화면에 띄울 값 + private final String rawDataValue; // 원천데이터 값rivate final String value; + +} From 53c27181c11bd4ed57858a4d543b7fe3cff0e893 Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 16 Oct 2025 02:58:00 +0900 Subject: [PATCH 02/32] =?UTF-8?q?feat=20:=20fast=20api=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=9B=EC=95=84=EC=98=AC=20DTO=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/dto/fast/FastPanelResponseDTO.java | 20 +++++++++++++++++++ .../back_end/domain/panel/entity/Panel.java | 19 +++++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java new file mode 100644 index 0000000..80b9eb4 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java @@ -0,0 +1,20 @@ +package DiffLens.back_end.domain.panel.dto.fast; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +public class FastPanelResponseDTO { + + /** + * fast api에서 패널 식별자 목록을 불러와 db에서 패널 데이터를 불러옵니다. + */ + @Getter + @Setter + public static class PanelList{ + List panelList; + Float accuracy; // 일치율 + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java index c99d2b3..2e00f80 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java @@ -25,33 +25,32 @@ public class Panel { private Object rawData; @Enumerated(EnumType.STRING) - @Column(nullable = false) private Gender gender; - @Column(nullable = false, length = 20) + @Column(length = 20) private String ageGroup; - @Column(nullable = false, length = 100) + @Column(length = 100) private String residence; - @Column(nullable = false, length = 10) + @Column(length = 10) private String martialStatus; - @Column(nullable = false) + @Column @Min(0) private Integer childrenCount; - @Column(nullable = false, length = 50) + @Column(length = 50) private String education; - @Column(nullable = false, length = 200) + @Column(length = 200) private String occupation; @JdbcTypeCode(SqlTypes.ARRAY) - @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") private List devices; - @Column(nullable = false, columnDefinition = "TEXT") + @Column(columnDefinition = "TEXT") private String profileSummary; @JdbcTypeCode(SqlTypes.VECTOR) @@ -59,7 +58,7 @@ public class Panel { private float[] embedding; // float[] 써야한다고 함... @JdbcTypeCode(SqlTypes.ARRAY) - @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") private List hashTags; From 7a74e6d8e1e3aeb1afe5961d95cca33d4a2077b8 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 18 Oct 2025 18:17:37 +0900 Subject: [PATCH 03/32] =?UTF-8?q?feat=20:=20=EC=9E=90=EC=97=B0=EC=96=B4=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20api=20=EC=9D=BC=EB=B6=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=20=20-=20Summary=20=20=20-=20Filter=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=20=20-=20=EC=B0=A8=ED=8A=B8=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=20=20-=20=EA=B0=9C=EB=B3=84=20=EC=9D=91=EB=8B=B5=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=8A=94=20=EA=B8=B0=ED=9A=8D=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=20=ED=9B=84=20=EC=B2=98=EB=A6=AC=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/dto/fast/FastPanelRequestDTO.java | 6 ++ .../panel/dto/fast/FastPanelResponseDTO.java | 31 +++++- .../panel/repository/PanelRepository.java | 20 ++++ .../domain/panel/util/ReflectionUtil.java | 18 ++++ .../search/controller/SearchController.java | 6 +- .../search/converter/ChartDtoConverter.java | 49 ++++++++++ .../search/converter/FilterDtoConverter.java | 37 +++++++ .../converter/PanelResponseConverter.java | 17 ++++ .../search/converter/SearchDtoConverter.java | 12 +++ .../search/converter/SummaryDtoConverter.java | 30 ++++++ .../domain/search/dto/SearchRequestDTO.java | 17 ++-- .../back_end/domain/search/entity/Chart.java | 66 +++++++++++++ .../back_end/domain/search/entity/Filter.java | 5 +- .../domain/search/entity/SearchHistory.java | 21 ++++ .../domain/search/enums/chart/ChartType.java | 9 ++ .../domain/search/enums/filters/Children.java | 2 +- .../search/enums/filters/FilterKey.java | 40 +++++++- .../search/enums/filters/Occupation.java | 10 +- .../search/repository/ChartRepository.java | 7 ++ .../search/repository/FilterRepository.java | 3 + .../repository/SearchFilterRepository.java | 8 ++ .../repository/SearchHistoryRepository.java | 7 ++ .../service/implement/ChartServiceImpl.java | 97 +++++++++++++++++++ .../implement/ExistingSearchService.java | 24 +++++ .../service/implement/FilterServiceImpl.java | 38 ++++++++ .../implement/NaturalSearchService.java | 79 +++++++++++++++ .../implement/SearchHistoryServiceImpl.java | 42 ++++++++ .../service/interfaces/ChartService.java | 12 +++ .../service/interfaces/FilterService.java | 12 +++ .../interfaces/SearchHistoryService.java | 10 ++ .../service/interfaces/SearchService.java | 9 ++ .../global/util/data/FilterInitializer.java | 24 ++++- 32 files changed, 743 insertions(+), 25 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/panel/util/ReflectionUtil.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/converter/ChartDtoConverter.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/converter/SearchDtoConverter.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/entity/Chart.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/repository/ChartRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/repository/SearchFilterRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/ExistingSearchService.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/interfaces/FilterService.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchService.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java new file mode 100644 index 0000000..0aae021 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java @@ -0,0 +1,6 @@ +package DiffLens.back_end.domain.panel.dto.fast; + +public class FastPanelRequestDTO { + + +} diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java index 80b9eb4..5c02201 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java @@ -12,9 +12,34 @@ public class FastPanelResponseDTO { */ @Getter @Setter - public static class PanelList{ - List panelList; - Float accuracy; // 일치율 + public static class SearchFastResult { + + // 일반적인 데이터 + private List panelList; + private Float accuracy; // 일치율 + + // 차트 관련 + private List charts; + + // 개별응답 종류 + // TODO : 개별응답 데이터에 어떤 데이터를 넣어야 할지 의논 후 추가 + private List panelColumns; + } + /** + * 차트 + */ + @Getter + @Setter + public static class CharFastResult { + private String chartType; // 차트 유형 + private String title; // 차트 제목 + private String reason; // 차트 선정 이유 + private String xAxis; // x축 항목 + private String yAxis; // y축 항목 + private String panelColumn; // 표에 표시할 정보 ex) 직업분포, 월평균 소득 등 + } + + } diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java new file mode 100644 index 0000000..ef417a2 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java @@ -0,0 +1,20 @@ +package DiffLens.back_end.domain.panel.repository; + +import DiffLens.back_end.domain.panel.entity.Panel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface PanelRepository extends JpaRepository { + + @Query( + """ + SELECT p FROM Panel p + WHERE p.id IN :ids + """ + ) + public List findByIdList(@Param("ids") List ids); + +} diff --git a/src/main/java/DiffLens/back_end/domain/panel/util/ReflectionUtil.java b/src/main/java/DiffLens/back_end/domain/panel/util/ReflectionUtil.java new file mode 100644 index 0000000..935d71a --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/util/ReflectionUtil.java @@ -0,0 +1,18 @@ +package DiffLens.back_end.domain.panel.util; + +import java.lang.reflect.Method; + +public class ReflectionUtil { + + public static Object getFieldValue(Object target, String fieldName) { + try { + String methodName = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + Method getter = target.getClass().getMethod(methodName); + return getter.invoke(target); + } catch (Exception e) { + return null; // 없는 필드나 접근 불가한 경우 null 반환 + } + } + + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java index 97c7025..f12d08c 100644 --- a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java +++ b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java @@ -2,6 +2,7 @@ import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import DiffLens.back_end.domain.search.service.interfaces.SearchService; import DiffLens.back_end.global.responses.exception.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,10 +16,13 @@ @RequiredArgsConstructor public class SearchController { + private final SearchService naturalServiceService; + private final SearchService existingSearchService; + @PostMapping @Operation(summary = "자연어 검색 ( 미구현 )") public ApiResponse naturalLanguage(@RequestBody @Valid SearchRequestDTO.NaturalLanguage request) { - SearchResponseDTO.SearchResult result = new SearchResponseDTO.SearchResult(); // 임시 result + SearchResponseDTO.SearchResult result = naturalServiceService.search(request); return ApiResponse.onSuccess(result); } diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/ChartDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/ChartDtoConverter.java new file mode 100644 index 0000000..f8f9f59 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/converter/ChartDtoConverter.java @@ -0,0 +1,49 @@ +package DiffLens.back_end.domain.search.converter; + +import DiffLens.back_end.domain.search.dto.ChartDTO; +import DiffLens.back_end.domain.search.entity.Chart; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class ChartDtoConverter implements SearchDtoConverter, List> { + + @Override + public List requestToDto(Void response, List charts) { + return charts.stream() + .map(this::chartToDto) + .toList(); + } + + private ChartDTO.Graph chartToDto(Chart chart) { + return ChartDTO.Graph.builder() + .chartId("") + .reason(chart.getReason()) + .chartType(chart.getChartType().name()) + .title(chart.getTitle()) + .xAxis(chart.getXAxis()) + .yAxis(chart.getYAxis()) + .dataPoints(getDataPoints(chart)) + .build(); + } + + private List getDataPoints(Chart chart) { + List dataPoints = new ArrayList<>(); + List labels = chart.getLabels(); + List values = chart.getValues(); + for(int i = 0 ; i < labels.size() ; i++) { + dataPoints.add(getDataPoint(labels.get(i), values.get(i))); + } + return dataPoints; + } + + private ChartDTO.DataPoint getDataPoint(String label, Integer value) { + return ChartDTO.DataPoint.builder() + .label(label) + .value(value) + .build(); + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java new file mode 100644 index 0000000..8559735 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java @@ -0,0 +1,37 @@ +package DiffLens.back_end.domain.search.converter; + +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO.SearchResult.AppliedFilter; +import DiffLens.back_end.domain.search.entity.Filter; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.service.interfaces.FilterService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class FilterDtoConverter implements SearchDtoConverter, SearchHistory>{ + + private final FilterService filterService; + + @Override + public List requestToDto(FastPanelResponseDTO.SearchFastResult response, SearchHistory searchHistory) { + + List filterIdList = searchHistory.getSearchFilter().getFilters(); + List filters = filterService.findFilters(filterIdList); + return filters.stream() + .map(this::filterToDto) + .toList(); + + } + + private AppliedFilter filterToDto(Filter filter) { + return AppliedFilter.builder() + .key(filter.getType()) + .displayValue(filter.getDisplayValue()) + .build(); + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java new file mode 100644 index 0000000..4bc3f60 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java @@ -0,0 +1,17 @@ +package DiffLens.back_end.domain.search.converter; + +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.search.dto.SearchPanelDTO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class PanelResponseConverter implements SearchDtoConverter>{ + @Override + public SearchPanelDTO.PanelData requestToDto(FastPanelResponseDTO.SearchFastResult response, List info) { + // TODO : 개별 응답데이터 처리 로직 작성 + return new SearchPanelDTO.PanelData(); + } +} diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/SearchDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/SearchDtoConverter.java new file mode 100644 index 0000000..be943a2 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/converter/SearchDtoConverter.java @@ -0,0 +1,12 @@ +package DiffLens.back_end.domain.search.converter; + +/** + * 검색 관련 DTO에 관한 converter + * + * T : 클라이언트 -> 서버 DTO + * R : fast api -> spring boot 웅덥 DTO + * S : 반환할 DTO + */ +public interface SearchDtoConverter { + public S requestToDto(R response, T info); +} diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java new file mode 100644 index 0000000..2df14f0 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java @@ -0,0 +1,30 @@ +package DiffLens.back_end.domain.search.converter; + + +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SummaryDtoConverter implements SearchDtoConverter> { + + @Override + public SearchResponseDTO.SearchResult.Summary requestToDto(FastPanelResponseDTO.SearchFastResult response, List panelList) { + + return SearchResponseDTO.SearchResult.Summary.builder() + .totalRespondents(panelList.size()) + .averageAge(getAgeAvg(panelList)) + .dataCaptureDate(null) + .confidenceLevel(response.getAccuracy().intValue()) + .build(); + } + + private Double getAgeAvg(List panelList) { + // TODO : 나이 평균 계산 + return 1.0; + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java index 64f6c32..6ae58f0 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java @@ -1,6 +1,5 @@ package DiffLens.back_end.domain.search.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -40,13 +39,15 @@ public static class ExistingSearchResult{ public static class SearchFilters{ private Integer count; private String gender; - @JsonProperty(value = "age_group") - private List ageGroup; - private List region; - @JsonProperty(value = "martial_status") - private List martialStatus; // 결혼상태 - private String children; - private List occupation;// 직업 + private List filters; // age_group:TWENTY +// private Respondent respondent; +// @JsonProperty(value = "age_group") +// private List ageGroup; +// private List region; +// @JsonProperty(value = "martial_status") +// private List martialStatus; // 결혼상태 +// private Children children; +// private List occupation;// 직업 // TODO : 추후 필터 추가 예정... diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java new file mode 100644 index 0000000..1204fe9 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java @@ -0,0 +1,66 @@ +package DiffLens.back_end.domain.search.entity; + +import DiffLens.back_end.domain.search.enums.chart.ChartType; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Chart { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String reason; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ChartType chartType; + + @Column(nullable = false, length = 50) + private String panelColumn; + + @Column(nullable = false, length = 50) + private String title; + + @Column(nullable = false, length = 50) + private String xAxis; + + @Column(nullable = false, length = 50) + private String yAxis; + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(nullable = false, columnDefinition = "text[]") + private List labels = new ArrayList<>(); + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(nullable = false, columnDefinition = "text[]") + private List values = new ArrayList<>(); + + + // 연관관계 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "history_id") + private SearchHistory searchHistory; + + // 연관관계 편의 메서드 + public void setSearchHistory(SearchHistory searchHistory) { + this.searchHistory = searchHistory; + + if (searchHistory != null && !searchHistory.getCharts().contains(this)) { + searchHistory.getCharts().add(this); + } + } + + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java index d3c5952..71d3bc8 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java @@ -17,6 +17,9 @@ public class Filter { private String type; @Column(nullable = false, length = 100) - private String value; + private String displayValue; + + @Column(nullable = false, length = 200) + private String rawDataValue; } diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java index 90d02d2..4cb9c39 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java @@ -33,8 +33,29 @@ public class SearchHistory extends BaseEntity { // 연관관계 + @OneToOne(cascade = CascadeType.REMOVE, mappedBy = "searchHistory") + private SearchFilter searchFilter; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; + @OneToMany(mappedBy = "searchHistory") + private List charts; + + public void setFilter(SearchFilter searchFilter) { + this.searchFilter = searchFilter; + } + + // 연관관계 편의 메서드 + public void addChart(Chart chart) { + charts.add(chart); + chart.setSearchHistory(this); + } + + public void removeChart(Chart chart) { + charts.remove(chart); + chart.setSearchHistory(null); + } + } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java b/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java index d128da3..ed0033f 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java @@ -1,4 +1,13 @@ package DiffLens.back_end.domain.search.enums.chart; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor public enum ChartType { + + BAR + ; + } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java index f4a91fb..d34822b 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Children.java @@ -16,4 +16,4 @@ public enum Children { private final String displayValue; // 화면에 띄울 값 private final String rawDataValue; // 원천데이터 값 -} +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java index ec30795..6768c32 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/FilterKey.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -16,7 +17,8 @@ public enum FilterKey { GENDER(300L, "gender", "성별", Gender.class), MARTIAL_STATUS(400L, "martial_status", "결혼상태", MartialStatus.class), OCCUPATION(500L, "occupation", "직업", Occupation.class), - RESIDENCE(600L, "residence", "거주지", Residence.class) + RESIDENCE(600L, "residence", "거주지", Residence.class), + RESPONDENT(700L, "respondent", "응답자수", Respondent.class), // 추가예정 ; @@ -30,4 +32,40 @@ public static List getFilterList(){ return Arrays.asList(FilterKey.values()); } + public static FilterKey findByKeyEn(String keyEn){ + return Arrays.stream(values()) + .filter(f -> f.getKeyEn().equals(keyEn)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Invalid keyEn: " + keyEn)); + } + + + public static List> getEnumList(){ + return new ArrayList>(Arrays.asList(FilterKey.values())); + } + + public static FilterKey findByEnumValue(String enumNameOrRawValue) { + for (FilterKey filterKey : FilterKey.values()) { + Class> enumClass = filterKey.getValueEnum(); + for (Enum enumConst : enumClass.getEnumConstants()) { + // enum 이름 또는 rawDataValue 비교 + try { + // "name()" 비교 + if (enumConst.name().equals(enumNameOrRawValue)) { + return filterKey; + } + // "rawDataValue" 비교 (만약 enum이 getRawDataValue() 메서드를 가지고 있다면) + Object rawDataValue = enumClass.getMethod("getRawDataValue").invoke(enumConst); + if (rawDataValue != null && rawDataValue.toString().equals(enumNameOrRawValue)) { + return filterKey; + } + } catch (Exception e) { + // getRawDataValue 없으면 무시 + } + } + } + throw new IllegalArgumentException("No FilterKey found for enum value: " + enumNameOrRawValue); + } + + } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java index add146c..54a3c82 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java @@ -9,11 +9,11 @@ public enum Occupation { STUDENT(1L, "학생", "학생"), - BUSINESS(1L, "직장인", "직장인"), - CEO(1L, "자영업", "자영업"), // 자영업자 - JUBU(1L, "주부", "주부"), // 주부 - BAEKSU(1L, "무직", "무직"), // 무직 - ETC(1L, "기타", "기타"), + BUSINESS(2L, "직장인", "직장인"), + CEO(3L, "자영업", "자영업"), // 자영업자 + JUBU(4L, "주부", "주부"), // 주부 + BAEKSU(5L, "무직", "무직"), // 무직 + ETC(6L, "기타", "기타"), ; diff --git a/src/main/java/DiffLens/back_end/domain/search/repository/ChartRepository.java b/src/main/java/DiffLens/back_end/domain/search/repository/ChartRepository.java new file mode 100644 index 0000000..4a64d8a --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/repository/ChartRepository.java @@ -0,0 +1,7 @@ +package DiffLens.back_end.domain.search.repository; + +import DiffLens.back_end.domain.search.entity.Chart; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChartRepository extends JpaRepository { +} diff --git a/src/main/java/DiffLens/back_end/domain/search/repository/FilterRepository.java b/src/main/java/DiffLens/back_end/domain/search/repository/FilterRepository.java index 5b03604..05e9e3a 100644 --- a/src/main/java/DiffLens/back_end/domain/search/repository/FilterRepository.java +++ b/src/main/java/DiffLens/back_end/domain/search/repository/FilterRepository.java @@ -2,6 +2,9 @@ import DiffLens.back_end.domain.search.entity.Filter; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; public interface FilterRepository extends JpaRepository { } diff --git a/src/main/java/DiffLens/back_end/domain/search/repository/SearchFilterRepository.java b/src/main/java/DiffLens/back_end/domain/search/repository/SearchFilterRepository.java new file mode 100644 index 0000000..3716149 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/repository/SearchFilterRepository.java @@ -0,0 +1,8 @@ +package DiffLens.back_end.domain.search.repository; + +import DiffLens.back_end.domain.search.entity.SearchFilter; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SearchFilterRepository extends JpaRepository { + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java b/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java new file mode 100644 index 0000000..abcc174 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java @@ -0,0 +1,7 @@ +package DiffLens.back_end.domain.search.repository; + +import DiffLens.back_end.domain.search.entity.SearchHistory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SearchHistoryRepository extends JpaRepository { +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java new file mode 100644 index 0000000..d268093 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java @@ -0,0 +1,97 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.util.ReflectionUtil; +import DiffLens.back_end.domain.search.entity.Chart; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.enums.chart.ChartType; +import DiffLens.back_end.domain.search.service.interfaces.ChartService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Method; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class ChartServiceImpl implements ChartService { + + @Override + public List makeChart(FastPanelResponseDTO.SearchFastResult searchResult, SearchHistory searchHistory, List foundPanels) { + + List fastApiChartResponse = searchResult.getCharts(); + + // fast api 로부터 받은 응답값을 List로 변환 + List charts = fastApiChartResponse.stream() + .map(chart -> + Chart.builder() + .reason(chart.getReason()) + .chartType(ChartType.valueOf(chart.getChartType())) + .panelColumn(chart.getPanelColumn()) + .title(chart.getTitle()) + .xAxis(chart.getXAxis()) + .yAxis(chart.getYAxis()) + .build() + ).toList(); + + // 차트 데이터 생성 및 삽입 + charts.forEach(chart -> { + + Map dataSet = makeDataSet(foundPanels, chart.getPanelColumn()); + + // label 오름차순 정렬하며 label과 value 쌍 보장 + dataSet.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> { + chart.getLabels().add(entry.getKey()); + chart.getValues().add(entry.getValue()); + }); + + + }); + + + return charts; + } + + /** + * + * @param foundPanels 조회할 패널 + * @param panelColumn 패널의 어떤 데이터를 조회할 것인지 + * @return Key : Label, Value : 수치 를 담은 Map + */ + private Map makeDataSet(List foundPanels, String panelColumn) { + + Map data = new LinkedHashMap<>(); + + foundPanels.forEach(panel -> { + try { + + // 문자열 형태의 column 정보로 panel 데이터 getter 메서드 호출 + Object value = ReflectionUtil.getFieldValue(panel, panelColumn); + + if (value instanceof List list) { + for (Object v : list) { + if (v != null) data.merge(v.toString(), 1, Integer::sum); + } + } else { + data.merge(value.toString(), 1, Integer::sum); + } + + } catch (Exception e) { + // 존재하지 않는 필드는 rawData에서 검색 시도 + if (panel.getRawData() instanceof Map rawMap) { + Object val = rawMap.get(panelColumn); + if (val != null) data.merge(val.toString(), 1, Integer::sum); + } + } + }); + + return data; + } + + + + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ExistingSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ExistingSearchService.java new file mode 100644 index 0000000..d6d5b6d --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ExistingSearchService.java @@ -0,0 +1,24 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import DiffLens.back_end.domain.search.service.interfaces.SearchService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 재검색 Service --> SearchService 구현 + */ +@Service("existingSearchService") +@RequiredArgsConstructor +public class ExistingSearchService implements SearchService { + + private final PanelRepository panelRepository; + + @Override + public SearchResponseDTO.SearchResult search(SearchRequestDTO.ExistingSearchResult request) { + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java new file mode 100644 index 0000000..03d2b87 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java @@ -0,0 +1,38 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.search.entity.Filter; +import DiffLens.back_end.domain.search.entity.SearchFilter; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.repository.FilterRepository; +import DiffLens.back_end.domain.search.repository.SearchFilterRepository; +import DiffLens.back_end.domain.search.service.interfaces.FilterService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class FilterServiceImpl implements FilterService { + + private final SearchFilterRepository searchFilterRepository; + private final FilterRepository filterRepository; + + @Override + public SearchFilter makeFilter(List filterIdList, SearchHistory searchHistory) { + + // SearchFilter 객체 생성 + SearchFilter searchFilter = SearchFilter.builder() + .id(searchHistory.getId()) + .filters(filterIdList) + .searchHistory(searchHistory) + .build(); + + return searchFilterRepository.save(searchFilter); + } + + @Override + public List findFilters(List filterIdList) { + return filterRepository.findAllById(filterIdList); + } +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java new file mode 100644 index 0000000..ea22576 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -0,0 +1,79 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.search.converter.SearchDtoConverter; +import DiffLens.back_end.domain.search.dto.ChartDTO; +import DiffLens.back_end.domain.search.dto.SearchPanelDTO; +import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import DiffLens.back_end.domain.search.entity.Chart; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.service.interfaces.ChartService; +import DiffLens.back_end.domain.search.service.interfaces.SearchHistoryService; +import DiffLens.back_end.domain.search.service.interfaces.SearchService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 자연어 Service --> SearchService 구현 + */ +@Service("naturalSearchService") +@RequiredArgsConstructor +public class NaturalSearchService implements SearchService { + + // service & repository + private final PanelRepository panelRepository; + private final SearchHistoryService searchHistoryService; + private final ChartService chartService; + + // converter들 + private final SearchDtoConverter> summaryConverter; + private final SearchDtoConverter, SearchHistory> filterConverter; + private final SearchDtoConverter, List> chartConverter; + private final SearchDtoConverter> panelResponseConverter; + + @Override + @Transactional(readOnly = false) + public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage request) { + + // TODO : 추후 fast api 에서 불러오도록 수정해야 함 + FastPanelResponseDTO.SearchFastResult response = new FastPanelResponseDTO.SearchFastResult(); + + // id List 추출 + List panelIdList = response.getPanelList(); + + // DB 에서 Panel 목록 가져옴 + List foundPanels = panelRepository.findByIdList(panelIdList); + // SearchHistory 데이터 생성 및 저장 + SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, panelIdList); + + // SearchResult.Summary 생성 + SearchResponseDTO.SearchResult.Summary summary = summaryConverter.requestToDto(response, foundPanels); + // SearchResult.AppliedFilter 생성 + List appliedFiltersSummary = filterConverter.requestToDto(response, searchHistory); + + // 차트 생성 + List charts = chartService.makeChart(response, searchHistory, foundPanels); + charts.forEach(searchHistory::addChart); // 연관관계 편의 메서드 호출 + + // 차트 변환 + List graphs = chartConverter.requestToDto(null, charts); + + // 개별 응답 데이터 처리 및 반환 + SearchPanelDTO.PanelData panelData = panelResponseConverter.requestToDto(response, foundPanels); + + return SearchResponseDTO.SearchResult.builder() + .searchId(searchHistory.getId()) + .summary(summary) + .appliedFiltersSummary(appliedFiltersSummary) + .charts(graphs) + .panelData(panelData) + .build(); + + } +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java new file mode 100644 index 0000000..829a98d --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -0,0 +1,42 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.entity.SearchFilter; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; +import DiffLens.back_end.domain.search.service.interfaces.FilterService; +import DiffLens.back_end.domain.search.service.interfaces.SearchHistoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SearchHistoryServiceImpl implements SearchHistoryService { + + private final SearchHistoryRepository historyRepository; + private final FilterService filterService; + + @Override + public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, List panelIdList) { + + // SearchHistory 데이터 생성 및 저장 + SearchHistory searchHistory = historyRepository.save( + SearchHistory.builder() + .date(LocalDate.now()) + .content(request.getQuestion()) + .panelIds(panelIdList) + .build() + ); + + // SearchFilter 생성 + SearchFilter searchFilter = filterService.makeFilter(request.getFilters().getFilters(), searchHistory); + // SearchHistory의 Filter를 지정 + searchHistory.setFilter(searchFilter); + + return searchHistory; + + } +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java new file mode 100644 index 0000000..ab4cabc --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java @@ -0,0 +1,12 @@ +package DiffLens.back_end.domain.search.service.interfaces; + +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.search.entity.Chart; +import DiffLens.back_end.domain.search.entity.SearchHistory; + +import java.util.List; + +public interface ChartService { + public List makeChart(FastPanelResponseDTO.SearchFastResult fastPanelResponseDTO, SearchHistory searchHistory, List foundPanels); +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/FilterService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/FilterService.java new file mode 100644 index 0000000..0c1bb0f --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/FilterService.java @@ -0,0 +1,12 @@ +package DiffLens.back_end.domain.search.service.interfaces; + +import DiffLens.back_end.domain.search.entity.Filter; +import DiffLens.back_end.domain.search.entity.SearchFilter; +import DiffLens.back_end.domain.search.entity.SearchHistory; + +import java.util.List; + +public interface FilterService { + SearchFilter makeFilter(List filterIdList, SearchHistory searchHistory); + List findFilters(List filterIdList); +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java new file mode 100644 index 0000000..b70aef2 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java @@ -0,0 +1,10 @@ +package DiffLens.back_end.domain.search.service.interfaces; + +import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.entity.SearchHistory; + +import java.util.List; + +public interface SearchHistoryService { + SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, List panelIdList); +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchService.java new file mode 100644 index 0000000..fecbf30 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchService.java @@ -0,0 +1,9 @@ +package DiffLens.back_end.domain.search.service.interfaces; + +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; + +import java.util.List; + +public interface SearchService { + SearchResponseDTO.SearchResult search(T request); +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java index 7735071..7e363d3 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java @@ -55,7 +55,8 @@ private List convertEnumToFilters(FilterKey filterKey) { filters.add(Filter.builder() .id(filterKey.getBaseId() + getEnumId(enumValue)) .type(filterKey.getKeyKr()) - .value(getEnumValue(enumValue)) + .displayValue(getDisplayDataValue(enumValue)) + .rawDataValue(getRawDataValue(enumValue)) .build()); } @@ -87,14 +88,27 @@ private Long getEnumId(Enum enumValue) { } /** - * Enum에서 getValue() 호출 + * Enum에서 getDisplayDataValue() 호출 */ - private String getEnumValue(Enum enumValue) { + private String getDisplayDataValue(Enum enumValue) { try { - Method valueMethod = enumValue.getClass().getMethod("getValue"); + Method valueMethod = enumValue.getClass().getMethod("getDisplayValue"); return (String) valueMethod.invoke(enumValue); } catch (Exception e) { - throw new RuntimeException("Enum value 조회 실패: " + enumValue.name(), e); + throw new RuntimeException("Enum DisplayDataValue 조회 실패: " + enumValue.name(), e); } } + + /** + * Enum에서 getRawDataValue() 호출 + */ + private String getRawDataValue(Enum enumValue) { + try { + Method valueMethod = enumValue.getClass().getMethod("getRawDataValue"); + return (String) valueMethod.invoke(enumValue); + } catch (Exception e) { + throw new RuntimeException("Enum RawDataValue 조회 실패: " + enumValue.name(), e); + } + } + } From a16d346fa20a7fd0de597b4306b23f4967b5335e Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 18 Oct 2025 20:22:56 +0900 Subject: [PATCH 04/32] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=9D=BC=EB=B6=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=20=20-=20=ED=8C=A8=EB=84=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EC=99=80=20=EC=9B=90=EC=B2=9C=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=20=20-=20=EB=88=84=EB=9D=BD=EB=90=9C=20Ba?= =?UTF-8?q?seEntity=20=EC=83=81=EC=86=8D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/library/entity/Library.java | 3 ++- .../domain/library/entity/LibraryPanel.java | 3 ++- .../back_end/domain/panel/entity/Panel.java | 13 ++++++---- .../back_end/domain/panel/entity/RawData.java | 25 +++++++++++++++++++ .../back_end/domain/search/entity/Chart.java | 3 ++- .../back_end/domain/search/entity/Filter.java | 3 ++- 6 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index 1f5d1d9..64d0c5d 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.library.entity; import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -9,7 +10,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Library { +public class Library extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/LibraryPanel.java b/src/main/java/DiffLens/back_end/domain/library/entity/LibraryPanel.java index b79d381..69ea73a 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/LibraryPanel.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/LibraryPanel.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.library.entity; import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -12,7 +13,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class LibraryPanel { +public class LibraryPanel extends BaseEntity { // 복합키 @EmbeddedId diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java index 2e00f80..878e130 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.panel.entity; import DiffLens.back_end.domain.search.enums.filters.Gender; +import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import jakarta.validation.constraints.Min; import lombok.*; @@ -14,16 +15,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Panel { +public class Panel extends BaseEntity { @Id @Column(length = 50) private String id; - @JdbcTypeCode(SqlTypes.JSON) - @Column(nullable = false) - private Object rawData; - @Enumerated(EnumType.STRING) private Gender gender; @@ -61,5 +58,11 @@ public class Panel { @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") private List hashTags; + // 연관관계 + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + @JoinColumn(name = "id") // FK 컬럼 이름을 Panel PK와 동일하게 + @MapsId + private RawData rawData; + } diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java b/src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java new file mode 100644 index 0000000..bbb03d5 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java @@ -0,0 +1,25 @@ +package DiffLens.back_end.domain.panel.entity; + +import DiffLens.back_end.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RawData extends BaseEntity { + + @Id + private String id; // Panel과 동일한 ID를 PK로 사용 + + @JdbcTypeCode(SqlTypes.JSON) + @Column(nullable = false) + private Object json; +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java index 1204fe9..5e8fe68 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.search.entity; import DiffLens.back_end.domain.search.enums.chart.ChartType; +import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.JdbcTypeCode; @@ -14,7 +15,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Chart { +public class Chart extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java index 71d3bc8..fe76dea 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.entity; +import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -8,7 +9,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Filter { +public class Filter extends BaseEntity { @Id private Long id; // 자동생성 X From af3e943bf53c4bc0190a8c0ed80ebcab16102e89 Mon Sep 17 00:00:00 2001 From: junyong Date: Wed, 22 Oct 2025 00:59:03 +0900 Subject: [PATCH 05/32] =?UTF-8?q?feat=20:=20json=20->=20Panel=20&=20RawDat?= =?UTF-8?q?a=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5(=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=EC=9A=A9)=20=EA=B5=AC=ED=98=84=20=20=20-=20applicatio?= =?UTF-8?q?n.yml=20=ED=8C=8C=EC=9D=BC=20=ED=81=AC=EA=B8=B0=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95=20=20=20-=20json=20->=20dto=20->?= =?UTF-8?q?=20panel&raw=5Fdata=20=EA=B5=AC=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back_end/domain/panel/entity/Panel.java | 12 +-- .../panel/repository/PanelRepository.java | 2 +- .../rawData/controller/RawDataController.java | 45 ++++++++++ .../domain/rawData/dto/ConsumptionDTO.java | 30 +++++++ .../domain/rawData/dto/DigitalDTO.java | 21 +++++ .../domain/rawData/dto/EnvironmentDTO.java | 15 ++++ .../domain/rawData/dto/HealthDTO.java | 30 +++++++ .../domain/rawData/dto/LifestyleDTO.java | 54 ++++++++++++ .../back_end/domain/rawData/dto/PanelDTO.java | 85 ++++++++++++++++++ .../domain/rawData/dto/SurveyResponseDTO.java | 24 ++++++ .../{panel => rawData}/entity/RawData.java | 8 +- .../rawData/parser/PanelJsonParser.java | 4 + .../rawData/repository/RawDataRepository.java | 14 +++ .../rawData/service/RawDataService.java | 9 ++ .../rawData/service/RawDataServiceImpl.java | 86 +++++++++++++++++++ .../back_end/domain/search/entity/Filter.java | 2 +- .../domain/search/enums/filters/Gender.java | 14 ++- .../search/enums/filters/MartialStatus.java | 13 +++ .../search/enums/filters/Occupation.java | 13 +++ .../service/implement/ChartServiceImpl.java | 1 - .../global/security/SecurityConfig.java | 1 + src/main/resources/application.yml | 4 + 22 files changed, 473 insertions(+), 14 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/ConsumptionDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/DigitalDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/EnvironmentDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/HealthDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/LifestyleDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/dto/SurveyResponseDTO.java rename src/main/java/DiffLens/back_end/domain/{panel => rawData}/entity/RawData.java (73%) create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java index 878e130..06774a5 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.panel.entity; +import DiffLens.back_end.domain.rawData.entity.RawData; import DiffLens.back_end.domain.search.enums.filters.Gender; import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; @@ -8,6 +9,7 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; +import java.util.ArrayList; import java.util.List; @Entity @@ -30,7 +32,7 @@ public class Panel extends BaseEntity { @Column(length = 100) private String residence; - @Column(length = 10) + @Column(length = 50) private String martialStatus; @Column @@ -45,21 +47,21 @@ public class Panel extends BaseEntity { @JdbcTypeCode(SqlTypes.ARRAY) @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") - private List devices; + private List devices = new ArrayList<>(); @Column(columnDefinition = "TEXT") private String profileSummary; @JdbcTypeCode(SqlTypes.VECTOR) @Column(columnDefinition = "vector(4096)") - private float[] embedding; // float[] 써야한다고 함... + private float[] embedding = new float[4096]; // float[] 써야한다고 함... @JdbcTypeCode(SqlTypes.ARRAY) @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") - private List hashTags; + private List hashTags = new ArrayList<>(); // 연관관계 - @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "id") // FK 컬럼 이름을 Panel PK와 동일하게 @MapsId private RawData rawData; diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java index ef417a2..7255a86 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java @@ -7,7 +7,7 @@ import java.util.List; -public interface PanelRepository extends JpaRepository { +public interface PanelRepository extends JpaRepository { @Query( """ diff --git a/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java new file mode 100644 index 0000000..ab554df --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java @@ -0,0 +1,45 @@ +package DiffLens.back_end.domain.rawData.controller; + +import DiffLens.back_end.domain.rawData.service.RawDataService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "관리자용 - 문제 시 문의바람") +@Slf4j +@RestController +@RequestMapping("/admin") +@RequiredArgsConstructor +public class RawDataController { + + private final RawDataService rawDataPanelService; + + @PostMapping(value = "/raw-data/upload", consumes = "multipart/form-data") + @Operation(summary = "원천데이터로.json 으로 Panel과 RawData 저장") + public ResponseEntity uploadJsonFile(@RequestParam("file") MultipartFile file) { + try { + // 파일 이름 확인 + String fileName = file.getOriginalFilename(); + log.info("Uploaded file: " + fileName); + + // JSON 내용 읽기 + String json = new String(file.getBytes()); + + // rawData 저장 + rawDataPanelService.uploadRawData(json); + + // 필요하면 파싱해서 객체로 변환 + // ObjectMapper mapper = new ObjectMapper(); + // Map data = mapper.readValue(json, Map.class); + + return ResponseEntity.ok("File uploaded successfully: " + fileName); + } catch (Exception e) { + return ResponseEntity.internalServerError().body("Error reading file: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/ConsumptionDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/ConsumptionDTO.java new file mode 100644 index 0000000..2d0704a --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/ConsumptionDTO.java @@ -0,0 +1,30 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ConsumptionDTO { + @JsonProperty("OTT개수") + private String ottCount; + + @JsonProperty("전통시장") + private String traditionalMarket; + + @JsonProperty("설선물") + private String holidayGift; + + @JsonProperty("소비만족") + private String consumptionSatisfaction; + + @JsonProperty("배송서비스") + private String deliveryService; + + @JsonProperty("주요지출") + private String mainExpense; + + @JsonProperty("포인트관심") + private String pointInterest; +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/DigitalDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/DigitalDTO.java new file mode 100644 index 0000000..25eb8fa --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/DigitalDTO.java @@ -0,0 +1,21 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class DigitalDTO { + @JsonProperty("자주쓰는앱") + private String frequentlyUsedApp; + + @JsonProperty("AI챗봇") + private String aiChatbot; + + @JsonProperty("AI활용") + private String aiUsage; + + @JsonProperty("개인정보보호") + private String privacyProtection; +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/EnvironmentDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/EnvironmentDTO.java new file mode 100644 index 0000000..7b4ae39 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/EnvironmentDTO.java @@ -0,0 +1,15 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class EnvironmentDTO { + @JsonProperty("버리기아까운물건") + private String itemNotThrownAway; + + @JsonProperty("비닐절감") + private String reducePlastic; +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/HealthDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/HealthDTO.java new file mode 100644 index 0000000..0f6b1e3 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/HealthDTO.java @@ -0,0 +1,30 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class HealthDTO { + @JsonProperty("체력관리") + private String fitness; + + @JsonProperty("피부상태") + private String skinCondition; + + @JsonProperty("땀불편") + private String sweatIssue; + + @JsonProperty("다이어트") + private String diet; + + @JsonProperty("야식방법") + private String lateSnackMethod; + + @JsonProperty("여름간식") + private String summerSnack; + + @JsonProperty("초콜릿섭취") + private String chocolateIntake; +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/LifestyleDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/LifestyleDTO.java new file mode 100644 index 0000000..0757685 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/LifestyleDTO.java @@ -0,0 +1,54 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LifestyleDTO { + @JsonProperty("겨울방학") + private String winterVacation; + + @JsonProperty("반려동물") + private String petOwnership; + + @JsonProperty("이사스트레스") + private String movingStress; + + @JsonProperty("스트레스원인") + private String stressCause; + + @JsonProperty("해외여행") + private String overseasTravel; + + @JsonProperty("여름걱정") + private String summerConcern; + + @JsonProperty("알람설정") + private String alarmSetting; + + @JsonProperty("혼밥빈도") + private String soloMealFrequency; + + @JsonProperty("노년행복") + private String seniorHappiness; + + @JsonProperty("라이프스타일") + private String lifestyleType; + + @JsonProperty("여행스타일") + private String travelStyle; + + @JsonProperty("여름패션") + private String summerFashion; + + @JsonProperty("비대처") + private String rainResponse; + + @JsonProperty("갤러리사진") + private String galleryPhoto; + + @JsonProperty("물놀이장소") + private String waterPlayLocation; +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java new file mode 100644 index 0000000..b850444 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java @@ -0,0 +1,85 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PanelDTO { + @JsonProperty("panel_id") + private String panelId; + + @JsonProperty("성별") + private String gender; + + @JsonProperty("나이") + private Integer age; + + @JsonProperty("연령대") + private String ageGroup; + + @JsonProperty("출생년도") + private Integer birthYear; + + @JsonProperty("거주지역") + private String residence; + + @JsonProperty("결혼여부") + private String maritalStatus; + + @JsonProperty("자녀수") + private Integer childrenCount; + + @JsonProperty("가족수") + private String familySize; + + @JsonProperty("최종학력") + private String education; + + @JsonProperty("직업") + private String occupation; + + @JsonProperty("직무") + private String job; + + @JsonProperty("개인소득") + private String personalIncome; + + @JsonProperty("가구소득") + private String householdIncome; + + @JsonProperty("보유전자제품") + private List ownedElectronics; + + @JsonProperty("휴대폰브랜드") + private String phoneBrand; + + @JsonProperty("휴대폰모델") + private String phoneModel; + + @JsonProperty("차량보유") + private String hasCar; + + @JsonProperty("차량브랜드") + private String carBrand; + + @JsonProperty("차량모델") + private String carModel; + + @JsonProperty("흡연경험") + private List smokingExperience; + + @JsonProperty("담배브랜드") + private List cigaretteBrands; + + @JsonProperty("전자담배") + private List eCigarettes; + + @JsonProperty("음주경험") + private List drinkingExperience; + + @JsonProperty("설문응답") + private SurveyResponseDTO surveyResponse; +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/SurveyResponseDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/SurveyResponseDTO.java new file mode 100644 index 0000000..8551118 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/SurveyResponseDTO.java @@ -0,0 +1,24 @@ +package DiffLens.back_end.domain.rawData.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SurveyResponseDTO { + @JsonProperty("건강생활") + private HealthDTO health; + + @JsonProperty("소비습관") + private ConsumptionDTO consumption; + + @JsonProperty("라이프스타일") + private LifestyleDTO lifestyle; + + @JsonProperty("디지털인식") + private DigitalDTO digital; + + @JsonProperty("환경의식") + private EnvironmentDTO environment; +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java b/src/main/java/DiffLens/back_end/domain/rawData/entity/RawData.java similarity index 73% rename from src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java rename to src/main/java/DiffLens/back_end/domain/rawData/entity/RawData.java index bbb03d5..1b2748b 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/RawData.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/entity/RawData.java @@ -1,11 +1,8 @@ -package DiffLens.back_end.domain.panel.entity; +package DiffLens.back_end.domain.rawData.entity; import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -21,5 +18,6 @@ public class RawData extends BaseEntity { @JdbcTypeCode(SqlTypes.JSON) @Column(nullable = false) + @Setter private Object json; } \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java b/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java new file mode 100644 index 0000000..bb0714e --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java @@ -0,0 +1,4 @@ +package DiffLens.back_end.domain.rawData.parser; + +public class PanelJsonParser { +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java b/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java new file mode 100644 index 0000000..c9c09e4 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java @@ -0,0 +1,14 @@ +package DiffLens.back_end.domain.rawData.repository; + +import DiffLens.back_end.domain.rawData.entity.RawData; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface RawDataRepository extends JpaRepository { + + @Query("delete from RawData") + @Modifying + void deleteAllRawData(); + +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java new file mode 100644 index 0000000..34eb415 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java @@ -0,0 +1,9 @@ +package DiffLens.back_end.domain.rawData.service; + +import java.io.IOException; + +public interface RawDataService { + + void uploadRawData(String json) throws IOException; + +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java new file mode 100644 index 0000000..11f2e35 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java @@ -0,0 +1,86 @@ +package DiffLens.back_end.domain.rawData.service; + +import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import DiffLens.back_end.domain.search.enums.filters.Gender; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.*; + +/** + * jpa 로 하면 메모리 터질거같아서 jdbc 씀 + * jdbcTemplate 메서드 호출하는 부분을 repository로 뺄까 했는데 복잡해질거같아서 해당 파일 안에 다 작성함. + */ +@Service +@RequiredArgsConstructor +public class RawDataServiceImpl implements RawDataService { + + private final JdbcTemplate jdbcTemplate; + private final ObjectMapper objectMapper; + + @Override + @Transactional + public void uploadRawData(String json) throws IOException { + + objectMapper.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()); + + // 1. 기존 RawData 삭제 + jdbcTemplate.update("TRUNCATE TABLE raw_data CASCADE"); + + // 2. 기존 Panel ID 조회 + List existingPanelIds = jdbcTemplate.queryForList("SELECT id FROM panel", String.class); + Set existingIds = new HashSet<>(existingPanelIds); + + // 3. JSON -> PanelDTO + List panelDTOList = objectMapper.readValue(json, new TypeReference>() {}); + + // 4. RawData bulk insert (jsonb 캐스팅) + String rawDataSql = "INSERT INTO raw_data (id, json) VALUES (?, ?::jsonb)"; + List rawDataBatch = new ArrayList<>(); + for (PanelDTO dto : panelDTOList) { + rawDataBatch.add(new Object[]{ + dto.getPanelId(), + objectMapper.writeValueAsString(dto) + }); + } + jdbcTemplate.batchUpdate(rawDataSql, rawDataBatch); + + // 5. Panel bulk insert (존재하지 않는 것만) + String panelSql = "INSERT INTO panel " + + "(id, gender, age_group, residence, martial_status, children_count, education, occupation, devices, profile_summary, embedding, hash_tags) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + List panelBatch = new ArrayList<>(); + for (PanelDTO dto : panelDTOList) { + if (!existingIds.contains(dto.getPanelId())) { + + String[] devicesArray = dto.getPhoneModel() != null ? new String[]{dto.getPhoneModel()} : new String[0]; + String[] hashTags = new String[0]; + // embedding은 0으로 초기화된 float 배열 + float[] embedding = new float[4096]; + + panelBatch.add(new Object[]{ + dto.getPanelId(), + Gender.fromRawDataValue(dto.getGender()).name(), + dto.getAgeGroup(), + dto.getResidence(), + dto.getMaritalStatus(), + dto.getChildrenCount(), + dto.getEducation(), + dto.getOccupation(), + devicesArray, + null, + embedding, + hashTags + }); + } + } + jdbcTemplate.batchUpdate(panelSql, panelBatch); + } +} diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java index fe76dea..a7dcb56 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Filter.java @@ -20,7 +20,7 @@ public class Filter extends BaseEntity { @Column(nullable = false, length = 100) private String displayValue; - @Column(nullable = false, length = 200) + @Column(nullable = true, length = 200) private String rawDataValue; } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java index 99bd583..0bf9dee 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java @@ -3,13 +3,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + @Getter @AllArgsConstructor public enum Gender { MALE(1L, "남성", "남성"), FEMALE(2L, "여성", "여성"), - NONE(3L, "알수없음", "알수없음") + NONE(3L, "알수없음", null) ; @@ -17,4 +19,14 @@ public enum Gender { private final String displayValue; // 화면에 띄울 값 private final String rawDataValue; // 원천데이터 값 + public static Gender fromRawDataValue(String rawDataValue) { + if (rawDataValue == null) { + return NONE; + } + return Arrays.stream(values()) + .filter(g -> rawDataValue.equals(g.getRawDataValue())) + .findFirst() + .orElse(NONE); + } + } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java index 1840a14..db89cb7 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java @@ -3,6 +3,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + @Getter @AllArgsConstructor public enum MartialStatus { @@ -11,6 +13,7 @@ public enum MartialStatus { MARRIED(2L, "기혼", "기혼"), DIVORCE(3L, "이혼", "이혼"), DEATH(4L, "사별", "사별"), + NONE(5L, "알수없음", null) ; @@ -18,4 +21,14 @@ public enum MartialStatus { private final String displayValue; // 화면에 띄울 값 private final String rawDataValue; // 원천데이터 값rivate final String value; + public static MartialStatus fromRawDataValue(String rawDataValue) { + if (rawDataValue == null) { + return NONE; + } + return Arrays.stream(values()) + .filter(m -> rawDataValue.equals(m.getRawDataValue())) + .findFirst() + .orElse(NONE); + } + } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java index 54a3c82..302134a 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Occupation.java @@ -3,6 +3,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + // 직업 @Getter @AllArgsConstructor @@ -14,6 +16,7 @@ public enum Occupation { JUBU(4L, "주부", "주부"), // 주부 BAEKSU(5L, "무직", "무직"), // 무직 ETC(6L, "기타", "기타"), + NONE(7L, "알수없음", null) ; @@ -21,4 +24,14 @@ public enum Occupation { private final String displayValue; // 화면에 띄울 값 private final String rawDataValue; // 원천데이터 값 + public static Occupation fromRawDataValue(String rawDataValue) { + if (rawDataValue == null) { + return NONE; + } + return Arrays.stream(values()) + .filter(g -> rawDataValue.equals(g.getRawDataValue())) + .findFirst() + .orElse(NONE); + } + } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java index d268093..3b8d215 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java @@ -10,7 +10,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.lang.reflect.Method; import java.util.*; @Service diff --git a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java index 026bb09..2f83e26 100644 --- a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java +++ b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java @@ -54,6 +54,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/auth/signup/**", "/auth/login/**", "/oauth2/**" +// "/admin/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 56ded67..0e6eca2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,10 @@ spring: profiles: active: local + servlet: + multipart: + max-file-size: 100MB # panel json 넣기 위한 설정 + max-request-size: 100MB datasource: driver-class-name: org.postgresql.Driver From a444d687a8af28c9bc94bbc287d14724a5ce012a Mon Sep 17 00:00:00 2001 From: junyong Date: Wed, 22 Oct 2025 01:31:03 +0900 Subject: [PATCH 06/32] =?UTF-8?q?feat=20:=20panelId=20->=20rawData=20->=20?= =?UTF-8?q?dto=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20service=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rawData/controller/RawDataController.java | 4 +- .../domain/rawData/converter/JsonToDTO.java | 29 ++++++ .../rawData/converter/RawDataConverter.java | 5 ++ .../rawData/parser/PanelJsonParser.java | 4 - .../rawData/service/RawDataService.java | 6 +- .../rawData/service/RawDataServiceImpl.java | 89 +++++-------------- .../rawData/service/RawDataUploadService.java | 9 ++ .../service/RawDataUploadServiceImpl.java | 86 ++++++++++++++++++ .../code/status/error/RawDataStatus.java | 38 ++++++++ 9 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/converter/RawDataConverter.java delete mode 100644 src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadService.java create mode 100644 src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java create mode 100644 src/main/java/DiffLens/back_end/global/responses/code/status/error/RawDataStatus.java diff --git a/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java index ab554df..3f601d4 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.rawData.controller; -import DiffLens.back_end.domain.rawData.service.RawDataService; +import DiffLens.back_end.domain.rawData.service.RawDataUploadService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -16,7 +16,7 @@ @RequiredArgsConstructor public class RawDataController { - private final RawDataService rawDataPanelService; + private final RawDataUploadService rawDataPanelService; @PostMapping(value = "/raw-data/upload", consumes = "multipart/form-data") @Operation(summary = "원천데이터로.json 으로 Panel과 RawData 저장") diff --git a/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java new file mode 100644 index 0000000..e40f5a5 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java @@ -0,0 +1,29 @@ +package DiffLens.back_end.domain.rawData.converter; + +import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@AllArgsConstructor +@NoArgsConstructor +public class JsonToDTO implements RawDataConverter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + + @Override + public PanelDTO convert(Object rawJson) { + try { + String jsonString = rawJson instanceof String ? + (String) rawJson : + objectMapper.writeValueAsString(rawJson); + return objectMapper.readValue(jsonString, PanelDTO.class); + } catch (Exception e) { + throw new RuntimeException("Failed to convert RawData JSON to PanelDTO", e); + } + + } +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/converter/RawDataConverter.java b/src/main/java/DiffLens/back_end/domain/rawData/converter/RawDataConverter.java new file mode 100644 index 0000000..d25fab7 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/converter/RawDataConverter.java @@ -0,0 +1,5 @@ +package DiffLens.back_end.domain.rawData.converter; + +public interface RawDataConverter { + T convert(V rawJson); +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java b/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java deleted file mode 100644 index bb0714e..0000000 --- a/src/main/java/DiffLens/back_end/domain/rawData/parser/PanelJsonParser.java +++ /dev/null @@ -1,4 +0,0 @@ -package DiffLens.back_end.domain.rawData.parser; - -public class PanelJsonParser { -} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java index 34eb415..35ac6e1 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java @@ -1,9 +1,7 @@ package DiffLens.back_end.domain.rawData.service; -import java.io.IOException; +import DiffLens.back_end.domain.rawData.dto.PanelDTO; public interface RawDataService { - - void uploadRawData(String json) throws IOException; - + PanelDTO getRawDataDTO(String panelId); } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java index 11f2e35..a394928 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java @@ -1,86 +1,39 @@ package DiffLens.back_end.domain.rawData.service; +import DiffLens.back_end.domain.rawData.converter.JsonToDTO; +import DiffLens.back_end.domain.rawData.converter.RawDataConverter; import DiffLens.back_end.domain.rawData.dto.PanelDTO; -import DiffLens.back_end.domain.search.enums.filters.Gender; -import com.fasterxml.jackson.core.json.JsonReadFeature; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import DiffLens.back_end.domain.rawData.entity.RawData; +import DiffLens.back_end.domain.rawData.repository.RawDataRepository; +import DiffLens.back_end.global.responses.code.status.error.RawDataStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; import lombok.RequiredArgsConstructor; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; -import java.util.*; - -/** - * jpa 로 하면 메모리 터질거같아서 jdbc 씀 - * jdbcTemplate 메서드 호출하는 부분을 repository로 뺄까 했는데 복잡해질거같아서 해당 파일 안에 다 작성함. - */ @Service @RequiredArgsConstructor public class RawDataServiceImpl implements RawDataService { - private final JdbcTemplate jdbcTemplate; - private final ObjectMapper objectMapper; + private final RawDataRepository rawDataRepository; + /** + * + * RawData를 pane_id로 조회하여 매핑된 DTO 객체를 반환합니다. + * + * @param panelId Panel 식별자 + * @return Json -> 클래스 매핑된 DTO + */ @Override - @Transactional - public void uploadRawData(String json) throws IOException { - - objectMapper.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()); - - // 1. 기존 RawData 삭제 - jdbcTemplate.update("TRUNCATE TABLE raw_data CASCADE"); - - // 2. 기존 Panel ID 조회 - List existingPanelIds = jdbcTemplate.queryForList("SELECT id FROM panel", String.class); - Set existingIds = new HashSet<>(existingPanelIds); - - // 3. JSON -> PanelDTO - List panelDTOList = objectMapper.readValue(json, new TypeReference>() {}); - - // 4. RawData bulk insert (jsonb 캐스팅) - String rawDataSql = "INSERT INTO raw_data (id, json) VALUES (?, ?::jsonb)"; - List rawDataBatch = new ArrayList<>(); - for (PanelDTO dto : panelDTOList) { - rawDataBatch.add(new Object[]{ - dto.getPanelId(), - objectMapper.writeValueAsString(dto) - }); - } - jdbcTemplate.batchUpdate(rawDataSql, rawDataBatch); - - // 5. Panel bulk insert (존재하지 않는 것만) - String panelSql = "INSERT INTO panel " + - "(id, gender, age_group, residence, martial_status, children_count, education, occupation, devices, profile_summary, embedding, hash_tags) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + @Transactional(readOnly = true) + public PanelDTO getRawDataDTO(String panelId) { - List panelBatch = new ArrayList<>(); - for (PanelDTO dto : panelDTOList) { - if (!existingIds.contains(dto.getPanelId())) { + RawData rawData = rawDataRepository.findById(panelId) + .orElseThrow(() -> new ErrorHandler(RawDataStatus.RAW_DATA_NOT_FOUND)); - String[] devicesArray = dto.getPhoneModel() != null ? new String[]{dto.getPhoneModel()} : new String[0]; - String[] hashTags = new String[0]; - // embedding은 0으로 초기화된 float 배열 - float[] embedding = new float[4096]; + Object json = rawData.getJson(); + RawDataConverter rawDataConverter = new JsonToDTO(); - panelBatch.add(new Object[]{ - dto.getPanelId(), - Gender.fromRawDataValue(dto.getGender()).name(), - dto.getAgeGroup(), - dto.getResidence(), - dto.getMaritalStatus(), - dto.getChildrenCount(), - dto.getEducation(), - dto.getOccupation(), - devicesArray, - null, - embedding, - hashTags - }); - } - } - jdbcTemplate.batchUpdate(panelSql, panelBatch); + return rawDataConverter.convert(json); } } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadService.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadService.java new file mode 100644 index 0000000..122f8db --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadService.java @@ -0,0 +1,9 @@ +package DiffLens.back_end.domain.rawData.service; + +import java.io.IOException; + +public interface RawDataUploadService { + + void uploadRawData(String json) throws IOException; + +} diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java new file mode 100644 index 0000000..e0c1b9e --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java @@ -0,0 +1,86 @@ +package DiffLens.back_end.domain.rawData.service; + +import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import DiffLens.back_end.domain.search.enums.filters.Gender; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.*; + +/** + * jpa 로 하면 메모리 터질거같아서 jdbc 씀 + * jdbcTemplate 메서드 호출하는 부분을 repository로 뺄까 했는데 복잡해질거같아서 해당 파일 안에 다 작성함. + */ +@Service +@RequiredArgsConstructor +public class RawDataUploadServiceImpl implements RawDataUploadService { + + private final JdbcTemplate jdbcTemplate; + private final ObjectMapper objectMapper; + + @Override + @Transactional + public void uploadRawData(String json) throws IOException { + + objectMapper.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()); + + // 1. 기존 RawData 삭제 + jdbcTemplate.update("TRUNCATE TABLE raw_data CASCADE"); + + // 2. 기존 Panel ID 조회 + List existingPanelIds = jdbcTemplate.queryForList("SELECT id FROM panel", String.class); + Set existingIds = new HashSet<>(existingPanelIds); + + // 3. JSON -> PanelDTO + List panelDTOList = objectMapper.readValue(json, new TypeReference>() {}); + + // 4. RawData bulk insert (jsonb 캐스팅) + String rawDataSql = "INSERT INTO raw_data (id, json) VALUES (?, ?::jsonb)"; + List rawDataBatch = new ArrayList<>(); + for (PanelDTO dto : panelDTOList) { + rawDataBatch.add(new Object[]{ + dto.getPanelId(), + objectMapper.writeValueAsString(dto) + }); + } + jdbcTemplate.batchUpdate(rawDataSql, rawDataBatch); + + // 5. Panel bulk insert (존재하지 않는 것만) + String panelSql = "INSERT INTO panel " + + "(id, gender, age_group, residence, martial_status, children_count, education, occupation, devices, profile_summary, embedding, hash_tags) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + List panelBatch = new ArrayList<>(); + for (PanelDTO dto : panelDTOList) { + if (!existingIds.contains(dto.getPanelId())) { + + String[] devicesArray = dto.getPhoneModel() != null ? new String[]{dto.getPhoneModel()} : new String[0]; + String[] hashTags = new String[0]; + // embedding은 0으로 초기화된 float 배열 + float[] embedding = new float[4096]; + + panelBatch.add(new Object[]{ + dto.getPanelId(), + Gender.fromRawDataValue(dto.getGender()).name(), + dto.getAgeGroup(), + dto.getResidence(), + dto.getMaritalStatus(), + dto.getChildrenCount(), + dto.getEducation(), + dto.getOccupation(), + devicesArray, + null, + embedding, + hashTags + }); + } + } + jdbcTemplate.batchUpdate(panelSql, panelBatch); + } +} diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/RawDataStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/RawDataStatus.java new file mode 100644 index 0000000..7b80cb3 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/RawDataStatus.java @@ -0,0 +1,38 @@ +package DiffLens.back_end.global.responses.code.status.error; + +import DiffLens.back_end.global.responses.code.BaseErrorCode; +import DiffLens.back_end.global.responses.code.ErrorReasonDto; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum RawDataStatus implements BaseErrorCode { + + RAW_DATA_ERROR(HttpStatus.BAD_REQUEST, "RAW400", "원천데이터 오류입니다. 관리자에게 문의하세요"), + RAW_DATA_NOT_FOUND(HttpStatus.NOT_FOUND, "RAW401", "존재하지 않는 원천데이터입니다."), + + RAW_DATA_INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "RAW500", "원천데이터 오류입니다. 관리자에게 문의하세요."), + + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDto getReason() { + return ErrorReasonDto.builder().message(message).code(code).isSuccess(false).build(); + } + + @Override + public ErrorReasonDto getReasonHttpStatus() { + return ErrorReasonDto.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build(); + } +} From 21648a67f7a63b5b0cfd05376553f7fa2ef27f4b Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 02:39:32 +0900 Subject: [PATCH 07/32] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=ED=85=8C=EC=9D=B4=EB=B8=94=EA=B3=BC=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/library/entity/Library.java | 9 +++---- .../library/entity/SearchHistoryLibrary.java | 27 +++++++++++++++++++ .../entity/SearchHistoryLibraryKey.java | 23 ++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java create mode 100644 src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibraryKey.java diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index 64d0c5d..197b9e1 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.library.entity; -import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.members.entity.Member; import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -23,8 +23,7 @@ public class Library extends BaseEntity { private String tag; // 연관관계 - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "history_id") - private SearchHistory history; - + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; } diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java new file mode 100644 index 0000000..cc59ba2 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java @@ -0,0 +1,27 @@ +package DiffLens.back_end.domain.library.entity; + +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SearchHistoryLibrary extends BaseEntity { + + @EmbeddedId + private SearchHistoryLibraryKey id; + + @MapsId("libraryId") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "library_id", insertable = false, updatable = false) + private Library library; + + @MapsId("historyId") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "history_id", insertable = false, updatable = false) + private SearchHistory history; +} diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibraryKey.java b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibraryKey.java new file mode 100644 index 0000000..952702b --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibraryKey.java @@ -0,0 +1,23 @@ +package DiffLens.back_end.domain.library.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode +@Embeddable +public class SearchHistoryLibraryKey implements Serializable { + @Column(name = "history_id") + private Long historyId; + + @Column(name = "library_id") + private Long libraryId; +} From c2693d8fba724cc153d50fc9c183a5820dbf76a5 Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 03:03:53 +0900 Subject: [PATCH 08/32] =?UTF-8?q?feat:=20=ED=83=9C=EA=B7=B8=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=B0=B0=EC=97=B4=20=ED=98=95=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiffLens/back_end/domain/library/entity/Library.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index 197b9e1..6087a74 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -4,6 +4,9 @@ import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.Type; + +import java.util.List; @Entity @Getter @@ -19,8 +22,8 @@ public class Library extends BaseEntity { @Column(nullable = false, length = 50) private String libraryName; - @Column(nullable = false, length = 10) - private String tag; + @Column(nullable = false, columnDefinition = "text[]") + private List tags; // 연관관계 @ManyToOne(fetch = FetchType.LAZY) From 9a2e134892b307c3974302664699678d2685900f Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 03:30:35 +0900 Subject: [PATCH 09/32] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/controller/LibraryController.java | 50 +++++++- .../domain/library/dto/LibraryRequestDto.java | 36 ++++++ .../library/dto/LibraryResponseDTO.java | 36 ++++++ .../repository/LibraryPanelRepository.java | 15 +++ .../library/repository/LibraryRepository.java | 7 ++ .../SearchHistoryLibraryRepository.java | 20 ++++ .../library/service/LibraryService.java | 113 ++++++++++++++++++ 7 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/library/dto/LibraryRequestDto.java create mode 100644 src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/library/repository/LibraryRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/library/repository/SearchHistoryLibraryRepository.java create mode 100644 src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java diff --git a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java index 8f40614..4f43786 100644 --- a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java +++ b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java @@ -1,13 +1,16 @@ package DiffLens.back_end.domain.library.controller; +import DiffLens.back_end.domain.library.dto.LibraryRequestDto; import DiffLens.back_end.domain.library.dto.LibraryResponseDTO; +import DiffLens.back_end.domain.library.service.LibraryService; +import DiffLens.back_end.domain.members.entity.Member; import DiffLens.back_end.global.responses.exception.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; @Tag(name = "라이브러리 API") @RestController @@ -15,11 +18,50 @@ @RequiredArgsConstructor public class LibraryController { + private final LibraryService libraryService; + @GetMapping - @Operation(summary = "라이브러리 목록 조회 ( 미구현 )") + @Operation(summary = "라이브러리 목록 조회") public ApiResponse libraryList( /* 파라미터 추가 필요 */ ){ LibraryResponseDTO.ListResult result = new LibraryResponseDTO.ListResult(); return ApiResponse.onSuccess(result); } + @PostMapping + @Operation(summary = "라이브러리 생성", + description = """ + + ## 개요 + 검색 기록을 기반으로 라이브러리를 생성하는 API입니다. + + ## Request Body + - search_history_id : 저장할 검색 기록의 ID (필수) + - library_name : 라이브러리 이름 (필수) + - tags : 라이브러리 분류 태그 리스트 (필수) + - panel_ids : 저장할 패널 ID 리스트 (선택, null이면 검색 기록의 모든 패널 저장) + + ## 응답 + - library_id : 생성된 라이브러리 ID + - library_name : 라이브러리 이름 + - search_history_id : 연결된 검색 기록 ID + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 + + ## 권한 + 본인의 검색 기록만 라이브러리로 저장할 수 있습니다. + + """) + public ApiResponse createLibrary( + @RequestBody @Valid LibraryRequestDto.Create request, + @AuthenticationPrincipal Member member + ) { + LibraryService.LibraryCreateResult createResult = libraryService.createLibrary(request, member); + + LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( + createResult.getLibrary(), + request.getSearchHistoryId(), + createResult.getPanelCount() + ); + return ApiResponse.onSuccess(result); + } } diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryRequestDto.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryRequestDto.java new file mode 100644 index 0000000..83a6a4c --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryRequestDto.java @@ -0,0 +1,36 @@ +package DiffLens.back_end.domain.library.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class LibraryRequestDto { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Create { + + @NotNull(message = "검색 기록 ID는 필수입니다") + @JsonProperty("search_history_id") + private Long searchHistoryId; + + @NotBlank(message = "라이브러리 이름은 필수입니다") + @JsonProperty("library_name") + private String libraryName; + + @NotNull(message = "태그는 필수입니다") + private List tags; + + @JsonProperty("panel_ids") + private List panelIds; // 선택적: null이면 SearchHistory의 panelIds 사용 + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index c3be2bc..8b180ca 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.library.dto; +import DiffLens.back_end.domain.library.entity.Library; import DiffLens.back_end.global.dto.ResponsePageDTO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -56,4 +57,39 @@ public static class Tag { } + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateResult { + + @JsonProperty("library_id") + private Long libraryId; + + @JsonProperty("library_name") + private String libraryName; + + private List tags; + + @JsonProperty("search_history_id") + private Long searchHistoryId; + + @JsonProperty("panel_count") + private Integer panelCount; + + @JsonProperty("created_at") + private String createdAt; + + public static CreateResult from(Library library, Long searchHistoryId, int panelCount) { + return CreateResult.builder() + .libraryId(library.getId()) + .libraryName(library.getLibraryName()) + .tags(library.getTags()) + .searchHistoryId(searchHistoryId) + .panelCount(panelCount) + .createdAt(library.getCreatedDate().toString()) + .build(); + } + } + } diff --git a/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java new file mode 100644 index 0000000..4c21e90 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java @@ -0,0 +1,15 @@ +package DiffLens.back_end.domain.library.repository; + +import DiffLens.back_end.domain.library.entity.LibraryPanel; +import DiffLens.back_end.domain.library.entity.LibraryPanelKey; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface LibraryPanelRepository extends JpaRepository { + + @Modifying + @Query("DELETE FROM LibraryPanel lp WHERE lp.library.id = :libraryId") + void deleteByLibraryId(@Param("libraryId") Long libraryId); +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/library/repository/LibraryRepository.java b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryRepository.java new file mode 100644 index 0000000..5addfca --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryRepository.java @@ -0,0 +1,7 @@ +package DiffLens.back_end.domain.library.repository; + +import DiffLens.back_end.domain.library.entity.Library; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LibraryRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/library/repository/SearchHistoryLibraryRepository.java b/src/main/java/DiffLens/back_end/domain/library/repository/SearchHistoryLibraryRepository.java new file mode 100644 index 0000000..7c931f6 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/repository/SearchHistoryLibraryRepository.java @@ -0,0 +1,20 @@ +package DiffLens.back_end.domain.library.repository; + +import DiffLens.back_end.domain.library.entity.SearchHistoryLibrary; +import DiffLens.back_end.domain.library.entity.SearchHistoryLibraryKey; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface SearchHistoryLibraryRepository extends JpaRepository { + + @Modifying + @Query("DELETE FROM SearchHistoryLibrary shl WHERE shl.library.id = :libraryId") + void deleteByLibraryId(@Param("libraryId") Long libraryId); + + @Query("SELECT shl FROM SearchHistoryLibrary shl WHERE shl.library.id = :libraryId") + List findByLibraryId(@Param("libraryId") Long libraryId); +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java new file mode 100644 index 0000000..a002286 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -0,0 +1,113 @@ +package DiffLens.back_end.domain.library.service; + +import DiffLens.back_end.domain.library.dto.LibraryRequestDto; +import DiffLens.back_end.domain.library.entity.Library; +import DiffLens.back_end.domain.library.entity.LibraryPanel; +import DiffLens.back_end.domain.library.entity.LibraryPanelKey; +import DiffLens.back_end.domain.library.entity.SearchHistoryLibrary; +import DiffLens.back_end.domain.library.entity.SearchHistoryLibraryKey; +import DiffLens.back_end.domain.library.repository.LibraryPanelRepository; +import DiffLens.back_end.domain.library.repository.LibraryRepository; +import DiffLens.back_end.domain.library.repository.SearchHistoryLibraryRepository; +import DiffLens.back_end.domain.members.entity.Member; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.search.entity.SearchHistory; +import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; +import DiffLens.back_end.global.responses.code.status.error.ErrorStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class LibraryService { + + private final LibraryRepository libraryRepository; + private final LibraryPanelRepository libraryPanelRepository; + private final SearchHistoryLibraryRepository searchHistoryLibraryRepository; + private final SearchHistoryRepository searchHistoryRepository; + private final PanelRepository panelRepository; + + @Transactional + public LibraryCreateResult createLibrary(LibraryRequestDto.Create request, Member member) { + + // 1. SearchHistory 검증 + SearchHistory history = searchHistoryRepository.findById(request.getSearchHistoryId()) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + // 2. 권한 검증 - 본인의 검색 기록만 라이브러리로 저장 가능 + if (!history.getMember().getId().equals(member.getId())) { + throw new ErrorHandler(ErrorStatus.FORBIDDEN); + } + + // 3. Library 생성 (SearchHistory 참조 제거) + Library library = Library.builder() + .libraryName(request.getLibraryName()) + .tags(request.getTags()) + .member(member) + .build(); + + library = libraryRepository.save(library); + + // 4. Library-SearchHistory 다대다 관계 생성 + SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() + .id(new SearchHistoryLibraryKey(history.getId(), library.getId())) + .library(library) + .history(history) + .build(); + + searchHistoryLibraryRepository.save(searchHistoryLibrary); + + // 5. Panel 관계 생성 + List panelIds = request.getPanelIds() != null + ? request.getPanelIds() + : history.getPanelIds(); // null이면 SearchHistory의 panelIds 사용 + + int panelCount = 0; + if (panelIds != null && !panelIds.isEmpty()) { + createLibraryPanels(library, panelIds); + panelCount = panelIds.size(); + } + + return new LibraryCreateResult(library, panelCount); + } + + private void createLibraryPanels(Library library, List panelIds) { + List panels = panelRepository.findByIdList(panelIds); + + if (panels.size() != panelIds.size()) { + // 존재하지 않는 패널 ID 찾기 + List foundIds = panels.stream() + .map(Panel::getId) + .toList(); + List missingIds = panelIds.stream() + .filter(id -> !foundIds.contains(id)) + .toList(); + + throw new ErrorHandler(ErrorStatus.BAD_REQUEST); + // TODO: 커스텀 에러 메시지 추가 시 활용 - "존재하지 않는 패널 ID: " + missingIds + } + + List libraryPanels = panels.stream() + .map(panel -> LibraryPanel.builder() + .id(new LibraryPanelKey(panel.getId(), library.getId())) + .library(library) + .panel(panel) + .build()) + .toList(); + + libraryPanelRepository.saveAll(libraryPanels); + } + + // 라이브러리 생성 결과를 담는 내부 클래스 + @lombok.Getter + @lombok.AllArgsConstructor + public static class LibraryCreateResult { + private final Library library; + private final int panelCount; + } +} \ No newline at end of file From adbd5dfd587f257540749301423d68a961746ae0 Mon Sep 17 00:00:00 2001 From: junyong Date: Wed, 22 Oct 2025 03:37:26 +0900 Subject: [PATCH 10/32] =?UTF-8?q?feat=20:=20=EA=B6=8C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=9D=B8=EC=A6=9D=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20&=20=EC=B4=88=EA=B8=B0=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=B6=94=EC=83=81?= =?UTF-8?q?=ED=99=94=20=20=20-=20USER,=20ADMIN=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EB=B6=84=EB=A6=AC=20=20=20-=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=20=20-?= =?UTF-8?q?=20Intializer=20=EC=B6=94=EC=83=81=ED=99=94=ED=95=98=EC=97=AC?= =?UTF-8?q?=20@PostConstruct=EC=97=90=EC=84=9C=20=ED=95=9C=20=EB=B2=88?= =?UTF-8?q?=EC=97=90=20=EC=8B=A4=ED=96=89=20=20=20-=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=20=EC=8B=A4=ED=96=89=20=EC=8B=9C=EC=A0=90=EC=97=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EA=B3=84=EC=A0=95=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=ED=95=98=EB=8A=94=20Initializer=20=EC=B6=94=EA=B0=80=20=20=20-?= =?UTF-8?q?=20RawData=20->=20PanelDTO=20api=EC=97=90=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EA=B6=8C=ED=95=9C=20=EB=B6=80=EC=97=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implement/AbstractSocialAuthStrategy.java | 2 + .../strategy/implement/AuthAdminStrategy.java | 56 +++++++++++++++++++ .../implement/AuthGeneralStrategy.java | 2 + .../domain/members/entity/Member.java | 7 ++- .../rawData/controller/RawDataController.java | 2 + .../domain/rawData/converter/JsonToDTO.java | 5 +- .../code/status/error/AuthStatus.java | 1 + .../security/JwtAuthenticationFilter.java | 30 ++++++++-- .../security/JwtTokenExpirationTime.java | 2 +- .../global/security/JwtTokenProvider.java | 7 +++ .../back_end/global/security/Role.java | 20 +++++++ .../global/security/SecurityConfig.java | 2 + .../global/util/data/FilterInitializer.java | 4 +- .../global/util/data/Initializer.java | 5 ++ .../global/util/data/MemberInitializer.java | 40 +++++++++++++ .../util/data/ServerDataInitializer.java | 34 +++++++++++ src/main/resources/application-dev.yml | 4 +- src/main/resources/application.yml | 6 +- 18 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java create mode 100644 src/main/java/DiffLens/back_end/global/security/Role.java create mode 100644 src/main/java/DiffLens/back_end/global/util/data/Initializer.java create mode 100644 src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java create mode 100644 src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java index aa3de53..9dd7d3d 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java @@ -9,6 +9,7 @@ import DiffLens.back_end.global.responses.code.status.error.AuthStatus; import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; import DiffLens.back_end.global.security.JwtTokenProvider; +import DiffLens.back_end.global.security.Role; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -91,6 +92,7 @@ protected Member createMember(SocialUserInfo userInfo) { .loginType(getLoginType()) .password(null) .loginType(userInfo.getLoginType()) + .role(Role.ROLE_USER) // .roles(Set.of(UserType.USER)) .build(); return memberRepository.save(member); diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java new file mode 100644 index 0000000..1b6d66f --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java @@ -0,0 +1,56 @@ +package DiffLens.back_end.domain.members.auth.strategy.implement; + +import DiffLens.back_end.domain.members.auth.strategy.interfaces.AuthStrategy; +import DiffLens.back_end.domain.members.dto.auth.AuthRequestDTO; +import DiffLens.back_end.domain.members.dto.auth.TokenResponseDTO; +import DiffLens.back_end.domain.members.entity.Member; +import DiffLens.back_end.domain.members.enums.LoginType; +import DiffLens.back_end.domain.members.repository.MemberRepository; +import DiffLens.back_end.global.responses.code.status.error.AuthStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; +import DiffLens.back_end.global.security.JwtTokenProvider; +import DiffLens.back_end.global.security.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthAdminStrategy implements AuthStrategy { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; + + @Override + public Boolean signUp(AuthRequestDTO.SignUp request) { + + // 요청 정보 추출 + String email = request.getEmail(); + LoginType loginType = request.getLoginType(); + + // 이미 존재하는 사용자 + if(memberRepository.existsByEmail(email)) { + throw new ErrorHandler(AuthStatus.ALREADY_EXISTS); + } + + // 유저 객체 생성 + Member member = Member.builder() + .email(email) + .name(request.getName()) + .loginType(loginType) + .password(passwordEncoder.encode(request.getPassword())) + .role(Role.ROLE_ADMIN) + .build(); + + // 유저 저장 + memberRepository.save(member); + return true; + + } + + @Override + public TokenResponseDTO login(Object request) { + throw new ErrorHandler(AuthStatus.NOT_SUPPLYING); + } +} diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java index 8942bff..e8e303a 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java @@ -9,6 +9,7 @@ import DiffLens.back_end.global.responses.code.status.error.AuthStatus; import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; import DiffLens.back_end.global.security.JwtTokenProvider; +import DiffLens.back_end.global.security.Role; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -39,6 +40,7 @@ public Boolean signUp(AuthRequestDTO.SignUp request) { .name(request.getName()) .loginType(loginType) .password(passwordEncoder.encode(request.getPassword())) + .role(Role.ROLE_USER) .build(); // 유저 저장 diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java index 448373b..4e08ac1 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java @@ -2,6 +2,7 @@ import DiffLens.back_end.domain.members.enums.LoginType; import DiffLens.back_end.global.entity.BaseEntity; +import DiffLens.back_end.global.security.Role; import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; @@ -39,6 +40,10 @@ public class Member extends BaseEntity implements UserDetails { @Column(nullable = true) private String profileImage; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; + // 연관관계 // 양방향 @@ -57,7 +62,7 @@ public class Member extends BaseEntity implements UserDetails { // Security 관련 @Override public Collection getAuthorities() { - return List.of(); + return List.of(role); } @Override diff --git a/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java index 3f601d4..cdcc14e 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/controller/RawDataController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -18,6 +19,7 @@ public class RawDataController { private final RawDataUploadService rawDataPanelService; + @PreAuthorize("hasRole('ADMIN')") @PostMapping(value = "/raw-data/upload", consumes = "multipart/form-data") @Operation(summary = "원천데이터로.json 으로 Panel과 RawData 저장") public ResponseEntity uploadJsonFile(@RequestParam("file") MultipartFile file) { diff --git a/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java index e40f5a5..2715b54 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java @@ -4,15 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor @AllArgsConstructor @NoArgsConstructor public class JsonToDTO implements RawDataConverter { - private final ObjectMapper objectMapper = new ObjectMapper(); - + private ObjectMapper objectMapper = new ObjectMapper(); @Override public PanelDTO convert(Object rawJson) { diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/AuthStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/AuthStatus.java index 9879599..7d512ad 100644 --- a/src/main/java/DiffLens/back_end/global/responses/code/status/error/AuthStatus.java +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/AuthStatus.java @@ -19,6 +19,7 @@ public enum AuthStatus implements BaseErrorCode { EXPIRED_TOKEN(HttpStatus.BAD_REQUEST, "AUTH405", "만료된 토큰입니다."), INVALID_TOKEN(HttpStatus.BAD_REQUEST, "AUTH406", "유효하지 않은 토큰입니다."), REFRESH_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "AUTH407", "올바르지 않은 refresh token 입니다."), + NOT_SUPPLYING(HttpStatus.BAD_REQUEST, "AUTH408", "제공하지 않는 기능입니다."), ERROR_IN_SIGNUP(HttpStatus.INTERNAL_SERVER_ERROR, "AUTH500", "회원가입 중 알 수 없는 오류가 발생했습니다."), diff --git a/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java b/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java index 478c570..b8c5faf 100644 --- a/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java @@ -9,6 +9,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; @@ -29,26 +30,26 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String token = resolveToken(request); try { if (token != null) { - // token 이 BlackList에 있을 경우 if (blackListService.isContainToken(token)) { throw new ErrorHandler(AuthStatus.EXPIRED_TOKEN); } if (jwtTokenProvider.validateToken(token)) { - Authentication authentication = jwtTokenProvider.getAuthentication(token); + // 단일 Role 기반 Authentication 생성 + Authentication authentication = getAuthenticationFromToken(token); SecurityContextHolder.getContext().setAuthentication(authentication); -// System.out.println("authentication = " + authentication.getAuthorities()); } else { -// System.out.println("Invalid or expired token."); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid or expired token."); return; } + } else { Authentication anonymousAuth = new AnonymousAuthenticationToken( @@ -62,9 +63,29 @@ protected void doFilterInternal( response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("Internal server error occurred."); } + filterChain.doFilter(request, response); } + /** + * JWT에서 단일 Role 기반 Authentication 생성 + */ + private Authentication getAuthenticationFromToken(String token) { + var claims = jwtTokenProvider.parseClaims(token); + String email = (String) claims.get("email"); + if (email == null) email = (String) claims.get("sub"); + + var member = jwtTokenProvider.getMemberByEmail(email); + + String roleStr = (String) claims.get("role"); + var authority = new SimpleGrantedAuthority(roleStr); + + return new UsernamePasswordAuthenticationToken( + member, null, Collections.singletonList(authority) + ); + } + + private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { @@ -72,4 +93,5 @@ private String resolveToken(HttpServletRequest request) { } return null; } + } diff --git a/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java b/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java index cd083ba..ef4ba42 100644 --- a/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java +++ b/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java @@ -8,7 +8,7 @@ public enum JwtTokenExpirationTime { ACCESS_TOKEN(1000L * 60 * 30), // 30분 - REFRESH_TOKEN(1000L * 60 * 60 * 24 * 14) + REFRESH_TOKEN(1000L * 60 * 60 * 24 * 14), ; private final long expirationMillis; diff --git a/src/main/java/DiffLens/back_end/global/security/JwtTokenProvider.java b/src/main/java/DiffLens/back_end/global/security/JwtTokenProvider.java index accc841..7c4abd3 100644 --- a/src/main/java/DiffLens/back_end/global/security/JwtTokenProvider.java +++ b/src/main/java/DiffLens/back_end/global/security/JwtTokenProvider.java @@ -133,6 +133,7 @@ public boolean validateToken(String token) { public String createAccessToken(Member member) { Claims claims = Jwts.claims().setSubject(member.getEmail()); + claims.put("role", member.getRole().name()); // 권한 설정 Date now = new Date(); return Jwts.builder() .setClaims(claims) @@ -235,6 +236,12 @@ public long getExpiration(String token) { } } + public Member getMemberByEmail(String email) { + return memberRepository.findByEmail(email) + .orElseThrow(() -> new ErrorHandler(AuthStatus.USER_NOT_FOUND)); + } + + public TokenWithRolesResponseDTO createTokenWithRoles(Member member) { // subject는 가급적 userId 기반 권장 (이메일 변경 이슈 방지). 기존 스타일을 그대로 쓰고 싶다면 이메일 유지도 가능. Claims claims = Jwts.claims().setSubject(String.valueOf(member.getId())); diff --git a/src/main/java/DiffLens/back_end/global/security/Role.java b/src/main/java/DiffLens/back_end/global/security/Role.java new file mode 100644 index 0000000..f00ea03 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/security/Role.java @@ -0,0 +1,20 @@ +package DiffLens.back_end.global.security; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; + +@Getter +@AllArgsConstructor +public enum Role implements GrantedAuthority { + + ROLE_USER, + ROLE_ADMIN, + ; + + @Override + public String getAuthority() { + return name(); + } + +} diff --git a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java index 2f83e26..24a036c 100644 --- a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java +++ b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; @@ -26,6 +27,7 @@ @EnableWebSecurity @Configuration @RequiredArgsConstructor +@EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { private final CustomLogoutHandler customLogoutHandler; diff --git a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java index 7e363d3..d3ec584 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java @@ -20,13 +20,13 @@ @Slf4j @Component @RequiredArgsConstructor -public class FilterInitializer { +public class FilterInitializer implements Initializer { private final FilterRepository filterRepository; @PostConstruct @Transactional - public void init() { + public void initialize() { log.info("[초기 필터 데이터 저장] 시작"); diff --git a/src/main/java/DiffLens/back_end/global/util/data/Initializer.java b/src/main/java/DiffLens/back_end/global/util/data/Initializer.java new file mode 100644 index 0000000..e723b4f --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/util/data/Initializer.java @@ -0,0 +1,5 @@ +package DiffLens.back_end.global.util.data; + +public interface Initializer { + void initialize(); +} diff --git a/src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java new file mode 100644 index 0000000..315c64a --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java @@ -0,0 +1,40 @@ +package DiffLens.back_end.global.util.data; + +import DiffLens.back_end.domain.members.auth.strategy.implement.AuthAdminStrategy; +import DiffLens.back_end.domain.members.dto.auth.AuthRequestDTO; +import DiffLens.back_end.domain.members.enums.LoginType; +import DiffLens.back_end.domain.members.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class MemberInitializer implements Initializer { + + private final AuthAdminStrategy authStrategy; + private final MemberRepository memberRepository; + + @Value("${auth.admin_email}") + private String EMAIL; + + @Value("${auth.admin_pw}") + private String PASSWORD; + + @Override + public void initialize() { + + Boolean isExist = memberRepository.existsByEmail(EMAIL); + + if(!isExist){ + AuthRequestDTO.SignUp admin = new AuthRequestDTO.SignUp( + EMAIL, + "admin", + PASSWORD, + LoginType.GENERAL + ); + authStrategy.signUp(admin); + } + + } +} diff --git a/src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java new file mode 100644 index 0000000..ad1ba3d --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java @@ -0,0 +1,34 @@ +package DiffLens.back_end.global.util.data; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * + * Initializer를 구현하는 모든 Bean을 찾아 List에 넣은 후 + * 각각 initialize() 메서드 실행 -> 기본 데이터 초기화 + * 서버 시작 시기에 초기화할 데이터가 있다면 Initializer 를 구현하면 됩니다. + * + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ServerDataInitializer { + + private final List initializers; + + @PostConstruct + @Transactional + public void initialize() { + log.info("[서버 초기 데이터 저장] 시작"); + for (Initializer initializer : initializers) { + initializer.initialize(); + } + log.info("[서버 초기 데이터 저장] 종료"); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 73eb48f..ff242d4 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -18,12 +18,10 @@ spring: data: redis: - host: ${REDIS_HOSTNAME} + host: ${REDIS_HOST} port: ${REDIS_PORT} timeout: 2000ms - - logging: level: root: info diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0e6eca2..bdc92b8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,4 +21,8 @@ spring: mode: never jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + +auth: + admin_email: ${ADMIN_EMAIL} + admin_pw: ${ADMIN_EMAIL} \ No newline at end of file From 0b12c44e5b57573a96cb28b7cef096181d566052 Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 03:39:32 +0900 Subject: [PATCH 11/32] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/controller/LibraryController.java | 29 +++++++++++++-- .../library/dto/LibraryResponseDTO.java | 37 +++++++++---------- .../repository/LibraryPanelRepository.java | 3 ++ .../library/repository/LibraryRepository.java | 4 ++ .../library/service/LibraryService.java | 18 +++++++++ 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java index 4f43786..f25a7d8 100644 --- a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java +++ b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java @@ -21,9 +21,32 @@ public class LibraryController { private final LibraryService libraryService; @GetMapping - @Operation(summary = "라이브러리 목록 조회") - public ApiResponse libraryList( /* 파라미터 추가 필요 */ ){ - LibraryResponseDTO.ListResult result = new LibraryResponseDTO.ListResult(); + @Operation(summary = "라이브러리 목록 조회", + description = """ + + ## 개요 + 인증된 사용자가 생성한 라이브러리 목록을 조회하는 API입니다. + + ## 응답 + - libraries : 라이브러리 목록 배열 + - library_id : 라이브러리 ID + - library_name : 라이브러리 이름 + - tags : 라이브러리 분류 태그 리스트 + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 + - page_info : 페이징 정보 (현재는 null, 추후 구현 예정) + + ## 권한 + 본인이 생성한 라이브러리만 조회할 수 있습니다. + + ## 정렬 + 최근 생성된 순서로 정렬됩니다. + + """) + public ApiResponse libraryList( + @AuthenticationPrincipal Member member + ){ + LibraryResponseDTO.ListResult result = libraryService.getLibrariesByMember(member); return ApiResponse.onSuccess(result); } diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index 8b180ca..c60d74a 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -18,7 +18,7 @@ public class LibraryResponseDTO { @AllArgsConstructor public static class ListResult{ - private List panels; + private List libraries; @JsonProperty("page_info") private ResponsePageDTO.PageInfo pageInfo; @@ -27,31 +27,30 @@ public static class ListResult{ @Builder @NoArgsConstructor @AllArgsConstructor - public static class Panel { + public static class LibraryItem { - @JsonProperty("search_id") - private Long searchId; + @JsonProperty("library_id") + private Long libraryId; - private String name; + @JsonProperty("library_name") + private String libraryName; - @JsonProperty("total_count") - private Integer totalCount; + private List tags; + + @JsonProperty("panel_count") + private Integer panelCount; @JsonProperty("created_at") private String createdAt; - @JsonProperty("data_type") - private String dataType; - - private List tags; - - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class Tag { - private String key; - private String value; + public static LibraryItem from(Library library, int panelCount) { + return LibraryItem.builder() + .libraryId(library.getId()) + .libraryName(library.getLibraryName()) + .tags(library.getTags()) + .panelCount(panelCount) + .createdAt(library.getCreatedDate().toString()) + .build(); } } diff --git a/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java index 4c21e90..a606e9e 100644 --- a/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java +++ b/src/main/java/DiffLens/back_end/domain/library/repository/LibraryPanelRepository.java @@ -12,4 +12,7 @@ public interface LibraryPanelRepository extends JpaRepository { + List findByMemberOrderByCreatedDateDesc(Member member); } \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index a002286..d9eca2e 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.library.service; import DiffLens.back_end.domain.library.dto.LibraryRequestDto; +import DiffLens.back_end.domain.library.dto.LibraryResponseDTO; import DiffLens.back_end.domain.library.entity.Library; import DiffLens.back_end.domain.library.entity.LibraryPanel; import DiffLens.back_end.domain.library.entity.LibraryPanelKey; @@ -76,6 +77,23 @@ public LibraryCreateResult createLibrary(LibraryRequestDto.Create request, Membe return new LibraryCreateResult(library, panelCount); } + @Transactional(readOnly = true) + public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { + List libraries = libraryRepository.findByMemberOrderByCreatedDateDesc(member); + + List libraryItems = libraries.stream() + .map(library -> { + int panelCount = libraryPanelRepository.countByLibraryId(library.getId()); + return LibraryResponseDTO.ListResult.LibraryItem.from(library, panelCount); + }) + .toList(); + + return LibraryResponseDTO.ListResult.builder() + .libraries(libraryItems) + .pageInfo(null) // 페이징이 필요하면 추후 구현 + .build(); + } + private void createLibraryPanels(Library library, List panelIds) { List panels = panelRepository.findByIdList(panelIds); From 9135297b7b05b374039aee3270a30265db8d1db1 Mon Sep 17 00:00:00 2001 From: junyong Date: Wed, 22 Oct 2025 04:09:50 +0900 Subject: [PATCH 12/32] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B2=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EC=8B=9C=EC=A0=90=EC=97=90=20PlanEnum=20->=20Plan?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20DB=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=8A=94=20Initializer=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=20=20-=20Plan=20=EC=97=94=ED=8B=B0=ED=8B=B0=20Id=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0(=20en?= =?UTF-8?q?um=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=99=80=20=EC=88=98?= =?UTF-8?q?=EB=8F=99=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95=20)=20=20=20-?= =?UTF-8?q?=20Plan(enum)=20->=20PlanEnum=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD=20=20=20-=20Member=EC=99=80=20?= =?UTF-8?q?=EC=9A=94=EA=B8=88=EC=A0=9C=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EC=A0=9C=EA=B1=B0=20->=20PlanEnum=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EB=84=A3=EC=96=B4=20enum=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B2=8C=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implement/AbstractSocialAuthStrategy.java | 5 ++- .../strategy/implement/AuthAdminStrategy.java | 1 + .../implement/AuthGeneralStrategy.java | 1 + .../members/dto/auth/AuthRequestDTO.java | 8 ++++ .../domain/members/entity/Member.java | 10 ++--- .../back_end/domain/members/entity/Plan.java | 1 - .../domain/members/enums/PlanEnum.java | 18 +++++++++ .../members/repository/PlanRepository.java | 7 ++++ .../{ => initializer}/FilterInitializer.java | 2 +- .../data/{ => initializer}/Initializer.java | 2 +- .../{ => initializer}/MemberInitializer.java | 6 ++- .../data/initializer/PlanInitializer.java | 39 +++++++++++++++++++ .../ServerDataInitializer.java | 2 +- src/main/resources/application-local.yml | 1 - 14 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java create mode 100644 src/main/java/DiffLens/back_end/domain/members/repository/PlanRepository.java rename src/main/java/DiffLens/back_end/global/util/data/{ => initializer}/FilterInitializer.java (98%) rename src/main/java/DiffLens/back_end/global/util/data/{ => initializer}/Initializer.java (50%) rename src/main/java/DiffLens/back_end/global/util/data/{ => initializer}/MemberInitializer.java (85%) create mode 100644 src/main/java/DiffLens/back_end/global/util/data/initializer/PlanInitializer.java rename src/main/java/DiffLens/back_end/global/util/data/{ => initializer}/ServerDataInitializer.java (94%) diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java index 9dd7d3d..f0b1363 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AbstractSocialAuthStrategy.java @@ -75,7 +75,7 @@ public TokenResponseDTO login(Object request) { // 멤버 유무 판단 후 create 메서드 호출 Member member = memberRepository.findByEmail(userInfo.getEmail()) - .orElseGet(() -> createMember(userInfo)); + .orElseGet(() -> createMember(userInfo, body)); return jwtTokenProvider.createToken(member); } @@ -85,7 +85,7 @@ public TokenResponseDTO login(Object request) { protected abstract SocialUserInfo getUserInfo(String accessToken); // member 생성 후 저장 - protected Member createMember(SocialUserInfo userInfo) { + protected Member createMember(SocialUserInfo userInfo, AuthRequestDTO.SocialLogin request) { Member member = Member.builder() .email(userInfo.getEmail()) .name(userInfo.getName()) @@ -94,6 +94,7 @@ protected Member createMember(SocialUserInfo userInfo) { .loginType(userInfo.getLoginType()) .role(Role.ROLE_USER) // .roles(Set.of(UserType.USER)) + .plan(request.getPlan()) .build(); return memberRepository.save(member); } diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java index 1b6d66f..a2497a1 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthAdminStrategy.java @@ -41,6 +41,7 @@ public Boolean signUp(AuthRequestDTO.SignUp request) { .loginType(loginType) .password(passwordEncoder.encode(request.getPassword())) .role(Role.ROLE_ADMIN) + .plan(request.getPlan()) .build(); // 유저 저장 diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java index e8e303a..b104faf 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java @@ -41,6 +41,7 @@ public Boolean signUp(AuthRequestDTO.SignUp request) { .loginType(loginType) .password(passwordEncoder.encode(request.getPassword())) .role(Role.ROLE_USER) + .plan(request.getPlan()) .build(); // 유저 저장 diff --git a/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java b/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java index ef5718b..686a528 100644 --- a/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.members.dto.auth; import DiffLens.back_end.domain.members.enums.LoginType; +import DiffLens.back_end.domain.members.enums.PlanEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -36,6 +37,10 @@ public static class SignUp { @NotNull(message = "로그인 타입은 필수 항목입니다.") private LoginType loginType; + @Schema(description = "플랜 유형") + @NotNull(message = "플랜 유형은 필수 항목입니다.") + private PlanEnum plan; + } @@ -71,6 +76,9 @@ public static class SocialLogin implements LoginRequest { @NotNull(message = "로그인 타입은 필수 항목입니다.") private LoginType loginType; + @NotNull(message = "플랜 유형은 필수 항목입니다.") + private PlanEnum plan; + } public interface LoginRequest { diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java index 4e08ac1..86b1ee9 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.members.entity; import DiffLens.back_end.domain.members.enums.LoginType; +import DiffLens.back_end.domain.members.enums.PlanEnum; import DiffLens.back_end.global.entity.BaseEntity; import DiffLens.back_end.global.security.Role; import jakarta.persistence.*; @@ -44,6 +45,10 @@ public class Member extends BaseEntity implements UserDetails { @Column(nullable = false) private Role role; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PlanEnum plan; + // 연관관계 // 양방향 @@ -54,11 +59,6 @@ public class Member extends BaseEntity implements UserDetails { @OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private Onboarding onboarding; - // 단방향 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "plan_id") - private Plan plan; - // Security 관련 @Override public Collection getAuthorities() { diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Plan.java b/src/main/java/DiffLens/back_end/domain/members/entity/Plan.java index 95ecfe4..f8ac405 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Plan.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Plan.java @@ -13,7 +13,6 @@ public class Plan extends BaseEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 50) diff --git a/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java b/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java new file mode 100644 index 0000000..878ec4f --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java @@ -0,0 +1,18 @@ +package DiffLens.back_end.domain.members.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter1 +@AllArgsConstructor +public enum PlanEnum { + + PERSONAL(1L, "개인", 100000), + BUSINESS(2L, "비즈니스", 500000) + ; + + private final Long id; + private final String name; + private final Integer price; + +} diff --git a/src/main/java/DiffLens/back_end/domain/members/repository/PlanRepository.java b/src/main/java/DiffLens/back_end/domain/members/repository/PlanRepository.java new file mode 100644 index 0000000..e325194 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/members/repository/PlanRepository.java @@ -0,0 +1,7 @@ +package DiffLens.back_end.domain.members.repository; + +import DiffLens.back_end.domain.members.entity.Plan; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlanRepository extends JpaRepository { +} diff --git a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java similarity index 98% rename from src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java rename to src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java index d3ec584..7b1a967 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/FilterInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java @@ -1,4 +1,4 @@ -package DiffLens.back_end.global.util.data; +package DiffLens.back_end.global.util.data.initializer; import DiffLens.back_end.domain.search.entity.Filter; import DiffLens.back_end.domain.search.enums.filters.FilterKey; diff --git a/src/main/java/DiffLens/back_end/global/util/data/Initializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/Initializer.java similarity index 50% rename from src/main/java/DiffLens/back_end/global/util/data/Initializer.java rename to src/main/java/DiffLens/back_end/global/util/data/initializer/Initializer.java index e723b4f..ebd9fc0 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/Initializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/Initializer.java @@ -1,4 +1,4 @@ -package DiffLens.back_end.global.util.data; +package DiffLens.back_end.global.util.data.initializer; public interface Initializer { void initialize(); diff --git a/src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java similarity index 85% rename from src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java rename to src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java index 315c64a..d4613a1 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/MemberInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java @@ -1,8 +1,9 @@ -package DiffLens.back_end.global.util.data; +package DiffLens.back_end.global.util.data.initializer; import DiffLens.back_end.domain.members.auth.strategy.implement.AuthAdminStrategy; import DiffLens.back_end.domain.members.dto.auth.AuthRequestDTO; import DiffLens.back_end.domain.members.enums.LoginType; +import DiffLens.back_end.domain.members.enums.PlanEnum; import DiffLens.back_end.domain.members.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -31,7 +32,8 @@ public void initialize() { EMAIL, "admin", PASSWORD, - LoginType.GENERAL + LoginType.GENERAL, + PlanEnum.BUSINESS ); authStrategy.signUp(admin); } diff --git a/src/main/java/DiffLens/back_end/global/util/data/initializer/PlanInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/PlanInitializer.java new file mode 100644 index 0000000..9f4dd56 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/PlanInitializer.java @@ -0,0 +1,39 @@ +package DiffLens.back_end.global.util.data.initializer; + +import DiffLens.back_end.domain.members.entity.Plan; +import DiffLens.back_end.domain.members.enums.PlanEnum; +import DiffLens.back_end.domain.members.repository.PlanRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; + +/** + * PlanEnum에 있는 값들 DB에 적용 + */ +@Component +@RequiredArgsConstructor +public class PlanInitializer implements Initializer { + + private final PlanRepository planRepository; + + @Override + @Transactional + public void initialize() { + + Arrays.stream(PlanEnum.values()) + .forEach(planEnum -> + planRepository.save( + Plan.builder() + .id(planEnum.getId()) + .name(planEnum.getName()) + .price(planEnum.getPrice()) + .build() + ) + ); + + } + + +} diff --git a/src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/ServerDataInitializer.java similarity index 94% rename from src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java rename to src/main/java/DiffLens/back_end/global/util/data/initializer/ServerDataInitializer.java index ad1ba3d..bb8cbcd 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/ServerDataInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/ServerDataInitializer.java @@ -1,4 +1,4 @@ -package DiffLens.back_end.global.util.data; +package DiffLens.back_end.global.util.data.initializer; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 5972db9..f64c6d1 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -17,7 +17,6 @@ spring: jpa: hibernate: ddl-auto: update - data: redis: host: localhost From 270cb6cad91d419f8849b4db4888fffdac5ed767 Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 18:05:20 +0900 Subject: [PATCH 13/32] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiffLens/back_end/domain/library/entity/Library.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index 6087a74..e04ecaf 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -4,7 +4,8 @@ import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; import java.util.List; @@ -25,6 +26,10 @@ public class Library extends BaseEntity { @Column(nullable = false, columnDefinition = "text[]") private List tags; + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") + private List panelIds; + // 연관관계 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") From bac6e53a2f5746d6541674fbf2f577991e6e37b3 Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 18:13:18 +0900 Subject: [PATCH 14/32] =?UTF-8?q?refact:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/dto/LibraryResponseDTO.java | 10 +- .../domain/library/entity/Library.java | 3 +- .../library/service/LibraryService.java | 184 +++++++++--------- 3 files changed, 100 insertions(+), 97 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index c60d74a..b5c0118 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -16,7 +16,7 @@ public class LibraryResponseDTO { @Builder @NoArgsConstructor @AllArgsConstructor - public static class ListResult{ + public static class ListResult { private List libraries; @@ -40,6 +40,9 @@ public static class LibraryItem { @JsonProperty("panel_count") private Integer panelCount; + @JsonProperty("panel_ids") + private List panelIds; + @JsonProperty("created_at") private String createdAt; @@ -49,6 +52,7 @@ public static LibraryItem from(Library library, int panelCount) { .libraryName(library.getLibraryName()) .tags(library.getTags()) .panelCount(panelCount) + .panelIds(library.getPanelIds()) .createdAt(library.getCreatedDate().toString()) .build(); } @@ -76,6 +80,9 @@ public static class CreateResult { @JsonProperty("panel_count") private Integer panelCount; + @JsonProperty("panel_ids") + private List panelIds; + @JsonProperty("created_at") private String createdAt; @@ -86,6 +93,7 @@ public static CreateResult from(Library library, Long searchHistoryId, int panel .tags(library.getTags()) .searchHistoryId(searchHistoryId) .panelCount(panelCount) + .panelIds(library.getPanelIds()) .createdAt(library.getCreatedDate().toString()) .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index e04ecaf..854a875 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -28,7 +28,8 @@ public class Library extends BaseEntity { @JdbcTypeCode(SqlTypes.ARRAY) @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") - private List panelIds; + @Builder.Default + private List panelIds = List.of(); // 연관관계 @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index d9eca2e..554c748 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -27,105 +27,99 @@ @RequiredArgsConstructor public class LibraryService { - private final LibraryRepository libraryRepository; - private final LibraryPanelRepository libraryPanelRepository; - private final SearchHistoryLibraryRepository searchHistoryLibraryRepository; - private final SearchHistoryRepository searchHistoryRepository; - private final PanelRepository panelRepository; - - @Transactional - public LibraryCreateResult createLibrary(LibraryRequestDto.Create request, Member member) { - - // 1. SearchHistory 검증 - SearchHistory history = searchHistoryRepository.findById(request.getSearchHistoryId()) - .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); - - // 2. 권한 검증 - 본인의 검색 기록만 라이브러리로 저장 가능 - if (!history.getMember().getId().equals(member.getId())) { - throw new ErrorHandler(ErrorStatus.FORBIDDEN); + private final LibraryRepository libraryRepository; + private final LibraryPanelRepository libraryPanelRepository; + private final SearchHistoryLibraryRepository searchHistoryLibraryRepository; + private final SearchHistoryRepository searchHistoryRepository; + private final PanelRepository panelRepository; + + @Transactional + public LibraryCreateResult createLibrary(LibraryRequestDto.Create request, Member member) { + + // 1. SearchHistory 검증 + SearchHistory history = searchHistoryRepository.findById(request.getSearchHistoryId()) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + // 2. 권한 검증 - 본인의 검색 기록만 라이브러리로 저장 가능 + if (!history.getMember().getId().equals(member.getId())) { + throw new ErrorHandler(ErrorStatus.FORBIDDEN); + } + + // 3. Library 생성 (SearchHistory 참조 제거) + // 패널 ID 결정: 요청에 있으면 사용, 없으면 검색기록의 패널 ID 사용 + List panelIds = request.getPanelIds() != null + ? request.getPanelIds() + : history.getPanelIds(); + + Library library = Library.builder() + .libraryName(request.getLibraryName()) + .tags(request.getTags()) + .panelIds(panelIds != null ? panelIds : List.of()) + .member(member) + .build(); + + library = libraryRepository.save(library); + + // 4. Library-SearchHistory 다대다 관계 생성 + SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() + .id(new SearchHistoryLibraryKey(history.getId(), library.getId())) + .library(library) + .history(history) + .build(); + + searchHistoryLibraryRepository.save(searchHistoryLibrary); + + // 5. Panel 관계 생성 + int panelCount = 0; + if (panelIds != null && !panelIds.isEmpty()) { + createLibraryPanels(library, panelIds); + panelCount = panelIds.size(); + } + + return new LibraryCreateResult(library, panelCount); } - // 3. Library 생성 (SearchHistory 참조 제거) - Library library = Library.builder() - .libraryName(request.getLibraryName()) - .tags(request.getTags()) - .member(member) - .build(); - - library = libraryRepository.save(library); - - // 4. Library-SearchHistory 다대다 관계 생성 - SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() - .id(new SearchHistoryLibraryKey(history.getId(), library.getId())) - .library(library) - .history(history) - .build(); - - searchHistoryLibraryRepository.save(searchHistoryLibrary); - - // 5. Panel 관계 생성 - List panelIds = request.getPanelIds() != null - ? request.getPanelIds() - : history.getPanelIds(); // null이면 SearchHistory의 panelIds 사용 - - int panelCount = 0; - if (panelIds != null && !panelIds.isEmpty()) { - createLibraryPanels(library, panelIds); - panelCount = panelIds.size(); + @Transactional(readOnly = true) + public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { + List libraries = libraryRepository.findByMemberOrderByCreatedDateDesc(member); + + List libraryItems = libraries.stream() + .map(library -> { + int panelCount = libraryPanelRepository.countByLibraryId(library.getId()); + return LibraryResponseDTO.ListResult.LibraryItem.from(library, panelCount); + }) + .toList(); + + return LibraryResponseDTO.ListResult.builder() + .libraries(libraryItems) + .pageInfo(null) // 페이징이 필요하면 추후 구현 + .build(); } - return new LibraryCreateResult(library, panelCount); - } - - @Transactional(readOnly = true) - public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { - List libraries = libraryRepository.findByMemberOrderByCreatedDateDesc(member); - - List libraryItems = libraries.stream() - .map(library -> { - int panelCount = libraryPanelRepository.countByLibraryId(library.getId()); - return LibraryResponseDTO.ListResult.LibraryItem.from(library, panelCount); - }) - .toList(); - - return LibraryResponseDTO.ListResult.builder() - .libraries(libraryItems) - .pageInfo(null) // 페이징이 필요하면 추후 구현 - .build(); - } - - private void createLibraryPanels(Library library, List panelIds) { - List panels = panelRepository.findByIdList(panelIds); - - if (panels.size() != panelIds.size()) { - // 존재하지 않는 패널 ID 찾기 - List foundIds = panels.stream() - .map(Panel::getId) - .toList(); - List missingIds = panelIds.stream() - .filter(id -> !foundIds.contains(id)) - .toList(); - - throw new ErrorHandler(ErrorStatus.BAD_REQUEST); - // TODO: 커스텀 에러 메시지 추가 시 활용 - "존재하지 않는 패널 ID: " + missingIds + private void createLibraryPanels(Library library, List panelIds) { + List panels = panelRepository.findByIdList(panelIds); + + if (panels.size() != panelIds.size()) { + throw new ErrorHandler(ErrorStatus.BAD_REQUEST); + // TODO: 커스텀 에러 메시지 추가 시 활용 - "존재하지 않는 패널 ID가 있습니다" + } + + List libraryPanels = panels.stream() + .map(panel -> LibraryPanel.builder() + .id(new LibraryPanelKey(panel.getId(), library.getId())) + .library(library) + .panel(panel) + .build()) + .toList(); + + libraryPanelRepository.saveAll(libraryPanels); } - List libraryPanels = panels.stream() - .map(panel -> LibraryPanel.builder() - .id(new LibraryPanelKey(panel.getId(), library.getId())) - .library(library) - .panel(panel) - .build()) - .toList(); - - libraryPanelRepository.saveAll(libraryPanels); - } - - // 라이브러리 생성 결과를 담는 내부 클래스 - @lombok.Getter - @lombok.AllArgsConstructor - public static class LibraryCreateResult { - private final Library library; - private final int panelCount; - } + // 라이브러리 생성 결과를 담는 내부 클래스 + @lombok.Getter + @lombok.AllArgsConstructor + public static class LibraryCreateResult { + private final Library library; + private final int panelCount; + } } \ No newline at end of file From e37ec2908a7f9e64386d08faf0cbdeceb2ccce07 Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 18:42:54 +0900 Subject: [PATCH 15/32] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EB=B3=91=ED=95=A9=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/controller/LibraryController.java | 119 ++++++++++++------ .../library/dto/LibraryResponseDTO.java | 2 +- .../library/service/LibraryService.java | 60 +++++++++ 3 files changed, 139 insertions(+), 42 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java index f25a7d8..3fc3d41 100644 --- a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java +++ b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java @@ -21,70 +21,107 @@ public class LibraryController { private final LibraryService libraryService; @GetMapping - @Operation(summary = "라이브러리 목록 조회", - description = """ + @Operation(summary = "라이브러리 목록 조회", description = """ - ## 개요 - 인증된 사용자가 생성한 라이브러리 목록을 조회하는 API입니다. + ## 개요 + 인증된 사용자가 생성한 라이브러리 목록을 조회하는 API입니다. - ## 응답 - - libraries : 라이브러리 목록 배열 - - library_id : 라이브러리 ID - - library_name : 라이브러리 이름 - - tags : 라이브러리 분류 태그 리스트 - - panel_count : 저장된 패널 개수 - - created_at : 생성 시간 - - page_info : 페이징 정보 (현재는 null, 추후 구현 예정) + ## 응답 + - libraries : 라이브러리 목록 배열 + - library_id : 라이브러리 ID + - library_name : 라이브러리 이름 + - tags : 라이브러리 분류 태그 리스트 + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 + - page_info : 페이징 정보 (현재는 null, 추후 구현 예정) - ## 권한 - 본인이 생성한 라이브러리만 조회할 수 있습니다. + ## 권한 + 본인이 생성한 라이브러리만 조회할 수 있습니다. - ## 정렬 - 최근 생성된 순서로 정렬됩니다. + ## 정렬 + 최근 생성된 순서로 정렬됩니다. - """) + """) public ApiResponse libraryList( - @AuthenticationPrincipal Member member - ){ + @AuthenticationPrincipal Member member) { LibraryResponseDTO.ListResult result = libraryService.getLibrariesByMember(member); return ApiResponse.onSuccess(result); } @PostMapping - @Operation(summary = "라이브러리 생성", - description = """ + @Operation(summary = "라이브러리 생성", description = """ - ## 개요 - 검색 기록을 기반으로 라이브러리를 생성하는 API입니다. + ## 개요 + 검색 기록을 기반으로 라이브러리를 생성하는 API입니다. - ## Request Body - - search_history_id : 저장할 검색 기록의 ID (필수) - - library_name : 라이브러리 이름 (필수) - - tags : 라이브러리 분류 태그 리스트 (필수) - - panel_ids : 저장할 패널 ID 리스트 (선택, null이면 검색 기록의 모든 패널 저장) + ## Request Body + - search_history_id : 저장할 검색 기록의 ID (필수) + - library_name : 라이브러리 이름 (필수) + - tags : 라이브러리 분류 태그 리스트 (필수) + - panel_ids : 저장할 패널 ID 리스트 (선택, null이면 검색 기록의 모든 패널 저장) - ## 응답 - - library_id : 생성된 라이브러리 ID - - library_name : 라이브러리 이름 - - search_history_id : 연결된 검색 기록 ID - - panel_count : 저장된 패널 개수 - - created_at : 생성 시간 + ## 응답 + - library_id : 생성된 라이브러리 ID + - library_name : 라이브러리 이름 + - search_history_id : 연결된 검색 기록 ID + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 - ## 권한 - 본인의 검색 기록만 라이브러리로 저장할 수 있습니다. + ## 권한 + 본인의 검색 기록만 라이브러리로 저장할 수 있습니다. - """) + """) public ApiResponse createLibrary( @RequestBody @Valid LibraryRequestDto.Create request, - @AuthenticationPrincipal Member member - ) { + @AuthenticationPrincipal Member member) { LibraryService.LibraryCreateResult createResult = libraryService.createLibrary(request, member); LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( createResult.getLibrary(), request.getSearchHistoryId(), - createResult.getPanelCount() - ); + createResult.getPanelCount()); + return ApiResponse.onSuccess(result); + } + + @PutMapping("/{libraryId}/search-histories/{searchHistoryId}") + @Operation(summary = "기존 라이브러리에 새로운 검색기록 추가(병합)", description = """ + + ## 개요 + 기존 라이브러리에 검색기록을 추가하는 API입니다. + 라이브러리의 패널 ID와 검색기록의 패널 ID를 병합하여 중복을 제거합니다. + + ## Path Parameters + - libraryId : 라이브러리 ID + - searchHistoryId : 추가할 검색기록 ID + + ## 응답 + - library_id : 라이브러리 ID + - library_name : 라이브러리 이름 + - search_history_id : 추가된 검색기록 ID + - panel_count : 새로 추가된 패널 개수 + - panel_ids : 병합된 전체 패널 ID 리스트 + - created_at : 생성 시간 + + ## 권한 + 본인의 라이브러리와 검색기록만 사용할 수 있습니다. + + ## 예시 + - 라이브러리 1번에 패널 ID [1, 2, 3]이 있음 + - 검색기록 2번에 패널 ID [3, 4, 5]가 있음 + - 결과: 라이브러리 1번에 패널 ID [1, 2, 3, 4, 5]가 됨 + + """) + public ApiResponse addSearchHistoryToLibrary( + @PathVariable Long libraryId, + @PathVariable Long searchHistoryId, + @AuthenticationPrincipal Member member) { + LibraryService.LibraryCreateResult createResult = libraryService.addSearchHistoryToLibrary(libraryId, + searchHistoryId, member); + + LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( + createResult.getLibrary(), + searchHistoryId, + createResult.getPanelCount()); return ApiResponse.onSuccess(result); } } diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index b5c0118..3632e8c 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -94,7 +94,7 @@ public static CreateResult from(Library library, Long searchHistoryId, int panel .searchHistoryId(searchHistoryId) .panelCount(panelCount) .panelIds(library.getPanelIds()) - .createdAt(library.getCreatedDate().toString()) + .createdAt(library.getCreatedDate() != null ? library.getCreatedDate().toString() : null) .build(); } } diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index 554c748..3fe9fbb 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -22,6 +22,8 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Service @RequiredArgsConstructor @@ -79,6 +81,64 @@ public LibraryCreateResult createLibrary(LibraryRequestDto.Create request, Membe return new LibraryCreateResult(library, panelCount); } + @Transactional + public LibraryCreateResult addSearchHistoryToLibrary(Long libraryId, Long searchHistoryId, Member member) { + // 1. 라이브러리 조회 및 권한 검증 + Library library = libraryRepository.findById(libraryId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + if (!library.getMember().getId().equals(member.getId())) { + throw new ErrorHandler(ErrorStatus.FORBIDDEN); + } + + // 2. 검색기록 조회 및 권한 검증 + SearchHistory searchHistory = searchHistoryRepository.findById(searchHistoryId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + if (!searchHistory.getMember().getId().equals(member.getId())) { + throw new ErrorHandler(ErrorStatus.FORBIDDEN); + } + + // 3. 패널 ID 병합 (중복 제거) + List existingPanelIds = library.getPanelIds(); + List newPanelIds = searchHistory.getPanelIds(); + List mergedPanelIds = Stream.concat( + existingPanelIds.stream(), + newPanelIds.stream()).distinct().collect(Collectors.toList()); + + // 4. 새로운 패널들만 LibraryPanel에 추가 + List newPanelsToAdd = newPanelIds.stream() + .filter(panelId -> !existingPanelIds.contains(panelId)) + .collect(Collectors.toList()); + + int addedPanelCount = 0; + if (!newPanelsToAdd.isEmpty()) { + createLibraryPanels(library, newPanelsToAdd); + addedPanelCount = newPanelsToAdd.size(); + } + + // 5. Library 엔티티의 panelIds 업데이트 + // Library 엔티티에 setter가 없으므로 새로운 객체로 생성 + Library updatedLibrary = Library.builder() + .id(library.getId()) + .libraryName(library.getLibraryName()) + .tags(library.getTags()) + .panelIds(mergedPanelIds) + .member(library.getMember()) + .build(); + library = libraryRepository.save(updatedLibrary); + + // 6. SearchHistoryLibrary 관계 생성 + SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() + .id(new SearchHistoryLibraryKey(searchHistoryId, libraryId)) + .library(library) + .history(searchHistory) + .build(); + searchHistoryLibraryRepository.save(searchHistoryLibrary); + + return new LibraryCreateResult(library, addedPanelCount); + } + @Transactional(readOnly = true) public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { List libraries = libraryRepository.findByMemberOrderByCreatedDateDesc(member); From e433f52e5fe9d34636920dedfc1d3ccf2611627f Mon Sep 17 00:00:00 2001 From: hardwoong Date: Wed, 22 Oct 2025 20:04:13 +0900 Subject: [PATCH 16/32] =?UTF-8?q?refact:=20CurrentUserService=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=8F=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=20=EA=B8=B0=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/controller/LibraryController.java | 15 +++++---- .../domain/library/entity/Library.java | 3 ++ .../library/entity/SearchHistoryLibrary.java | 2 ++ .../library/service/LibraryService.java | 32 +++++++++---------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java index 3fc3d41..9b05709 100644 --- a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java +++ b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java @@ -4,12 +4,12 @@ import DiffLens.back_end.domain.library.dto.LibraryResponseDTO; import DiffLens.back_end.domain.library.service.LibraryService; import DiffLens.back_end.domain.members.entity.Member; +import DiffLens.back_end.domain.members.service.auth.CurrentUserService; import DiffLens.back_end.global.responses.exception.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @Tag(name = "라이브러리 API") @@ -19,6 +19,7 @@ public class LibraryController { private final LibraryService libraryService; + private final CurrentUserService currentUserService; @GetMapping @Operation(summary = "라이브러리 목록 조회", description = """ @@ -42,8 +43,8 @@ public class LibraryController { 최근 생성된 순서로 정렬됩니다. """) - public ApiResponse libraryList( - @AuthenticationPrincipal Member member) { + public ApiResponse libraryList() { + Member member = currentUserService.getCurrentUser(); LibraryResponseDTO.ListResult result = libraryService.getLibrariesByMember(member); return ApiResponse.onSuccess(result); } @@ -72,8 +73,8 @@ public ApiResponse libraryList( """) public ApiResponse createLibrary( - @RequestBody @Valid LibraryRequestDto.Create request, - @AuthenticationPrincipal Member member) { + @RequestBody @Valid LibraryRequestDto.Create request) { + Member member = currentUserService.getCurrentUser(); LibraryService.LibraryCreateResult createResult = libraryService.createLibrary(request, member); LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( @@ -113,8 +114,8 @@ public ApiResponse createLibrary( """) public ApiResponse addSearchHistoryToLibrary( @PathVariable Long libraryId, - @PathVariable Long searchHistoryId, - @AuthenticationPrincipal Member member) { + @PathVariable Long searchHistoryId) { + Member member = currentUserService.getCurrentUser(); LibraryService.LibraryCreateResult createResult = libraryService.addSearchHistoryToLibrary(libraryId, searchHistoryId, member); diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java index 854a875..d95c2ec 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/Library.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/Library.java @@ -6,14 +6,17 @@ import lombok.*; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.util.List; @Entity @Getter +@Setter @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) public class Library extends BaseEntity { @Id diff --git a/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java index cc59ba2..f7989b0 100644 --- a/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java +++ b/src/main/java/DiffLens/back_end/domain/library/entity/SearchHistoryLibrary.java @@ -4,8 +4,10 @@ import DiffLens.back_end.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity +@EntityListeners(AuditingEntityListener.class) @Getter @Builder @AllArgsConstructor diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index 3fe9fbb..61cf881 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -118,23 +118,23 @@ public LibraryCreateResult addSearchHistoryToLibrary(Long libraryId, Long search } // 5. Library 엔티티의 panelIds 업데이트 - // Library 엔티티에 setter가 없으므로 새로운 객체로 생성 - Library updatedLibrary = Library.builder() - .id(library.getId()) - .libraryName(library.getLibraryName()) - .tags(library.getTags()) - .panelIds(mergedPanelIds) - .member(library.getMember()) - .build(); - library = libraryRepository.save(updatedLibrary); + // 기존 엔티티의 panelIds만 업데이트 (AuditingEntityListener가 updatedAt 자동 관리) + library.setPanelIds(mergedPanelIds); + library = libraryRepository.save(library); - // 6. SearchHistoryLibrary 관계 생성 - SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() - .id(new SearchHistoryLibraryKey(searchHistoryId, libraryId)) - .library(library) - .history(searchHistory) - .build(); - searchHistoryLibraryRepository.save(searchHistoryLibrary); + // 6. SearchHistoryLibrary 관계 생성 (존재하지 않을 때만) + SearchHistoryLibraryKey searchHistoryLibraryKey = new SearchHistoryLibraryKey(searchHistoryId, + libraryId); + boolean relationshipExists = searchHistoryLibraryRepository.existsById(searchHistoryLibraryKey); + + if (!relationshipExists) { + SearchHistoryLibrary searchHistoryLibrary = SearchHistoryLibrary.builder() + .id(searchHistoryLibraryKey) + .library(library) + .history(searchHistory) + .build(); + searchHistoryLibraryRepository.save(searchHistoryLibrary); + } return new LibraryCreateResult(library, addedPanelCount); } From 20d7f8b2d3aef49d86d497b1d17d5cd1b3bc8bf1 Mon Sep 17 00:00:00 2001 From: junyong Date: Wed, 22 Oct 2025 23:54:52 +0900 Subject: [PATCH 17/32] =?UTF-8?q?feat=20:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EA=B3=BC=EC=A0=95=20=ED=8F=AC=ED=95=A8=20=20=20-=20=EC=A7=81?= =?UTF-8?q?=EB=AC=B4&=EC=97=85=EC=A2=85=20=EC=B6=94=EA=B0=80=20=20=20-=20?= =?UTF-8?q?=EC=95=BD=EA=B4=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implement/AuthGeneralStrategy.java | 9 ++++++++ .../members/dto/auth/AuthRequestDTO.java | 12 +++++++++++ .../domain/members/entity/Agreement.java | 2 +- .../domain/members/entity/Member.java | 10 +++++++-- .../domain/members/entity/Onboarding.java | 7 +++++++ .../domain/members/enums/Industry.java | 20 +++++++++++++++++- .../back_end/domain/members/enums/Job.java | 21 ++++++++++++++++++- .../domain/members/enums/PlanEnum.java | 2 +- .../data/initializer/MemberInitializer.java | 6 +++++- 9 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java index b104faf..73ca333 100644 --- a/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java +++ b/src/main/java/DiffLens/back_end/domain/members/auth/strategy/implement/AuthGeneralStrategy.java @@ -3,7 +3,9 @@ import DiffLens.back_end.domain.members.auth.strategy.interfaces.AuthStrategy; import DiffLens.back_end.domain.members.dto.auth.AuthRequestDTO; import DiffLens.back_end.domain.members.dto.auth.TokenResponseDTO; +import DiffLens.back_end.domain.members.entity.Agreement; import DiffLens.back_end.domain.members.entity.Member; +import DiffLens.back_end.domain.members.entity.Onboarding; import DiffLens.back_end.domain.members.enums.LoginType; import DiffLens.back_end.domain.members.repository.MemberRepository; import DiffLens.back_end.global.responses.code.status.error.AuthStatus; @@ -44,6 +46,13 @@ public Boolean signUp(AuthRequestDTO.SignUp request) { .plan(request.getPlan()) .build(); + // Onboarding 생성 + Onboarding onboarding = Onboarding.builder() + .job(request.getJob()) + .industry(request.getIndustry()) + .build(); + onboarding.setMember(member); // 양방향 관계 편의 메서드로 연결 + // 유저 저장 memberRepository.save(member); return true; diff --git a/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java b/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java index 686a528..1c66f34 100644 --- a/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/members/dto/auth/AuthRequestDTO.java @@ -1,5 +1,7 @@ package DiffLens.back_end.domain.members.dto.auth; +import DiffLens.back_end.domain.members.enums.Industry; +import DiffLens.back_end.domain.members.enums.Job; import DiffLens.back_end.domain.members.enums.LoginType; import DiffLens.back_end.domain.members.enums.PlanEnum; import io.swagger.v3.oas.annotations.media.Schema; @@ -18,6 +20,7 @@ public class AuthRequestDTO { @AllArgsConstructor public static class SignUp { + // 로그인 정보 @Schema(description = "email 형식으로 입력해야 합니다.") @NotBlank(message = "이메일은 필수 입력 항목입니다.") @Email(message = "올바른 이메일 형식이 아닙니다.") @@ -37,10 +40,19 @@ public static class SignUp { @NotNull(message = "로그인 타입은 필수 항목입니다.") private LoginType loginType; + // 플랜 @Schema(description = "플랜 유형") @NotNull(message = "플랜 유형은 필수 항목입니다.") private PlanEnum plan; + // 온보딩 + @Schema(description = "직무") + @NotNull(message = "직무는 필수 항목입니다.") + private Job job; + + @Schema(description = "엄종") + @NotNull(message = "엄종은 필수 항목입니다.") + private Industry industry; } diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Agreement.java b/src/main/java/DiffLens/back_end/domain/members/entity/Agreement.java index 52893eb..f261253 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Agreement.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Agreement.java @@ -4,7 +4,7 @@ import jakarta.persistence.*; import lombok.*; -@Entity +//@Entity @Getter @Builder @AllArgsConstructor diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java index 86b1ee9..3572da1 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Member.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Member.java @@ -52,13 +52,19 @@ public class Member extends BaseEntity implements UserDetails { // 연관관계 // 양방향 - @OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private Agreement agreement; +// @OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) +// private Agreement agreement; // 양방향 @OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private Onboarding onboarding; + // 연관관계 편의 메서드 + public void setOnboarding(Onboarding onboarding) { + this.onboarding = onboarding; + } + + // Security 관련 @Override public Collection getAuthorities() { diff --git a/src/main/java/DiffLens/back_end/domain/members/entity/Onboarding.java b/src/main/java/DiffLens/back_end/domain/members/entity/Onboarding.java index 142f3af..0a4aa4c 100644 --- a/src/main/java/DiffLens/back_end/domain/members/entity/Onboarding.java +++ b/src/main/java/DiffLens/back_end/domain/members/entity/Onboarding.java @@ -32,4 +32,11 @@ public class Onboarding extends BaseEntity { @JoinColumn(name = "member_id", nullable = false) private Member member; + public void setMember(Member member) { + this.member = member; + if (member.getOnboarding() != this) { + member.setOnboarding(this); + } + } + } diff --git a/src/main/java/DiffLens/back_end/domain/members/enums/Industry.java b/src/main/java/DiffLens/back_end/domain/members/enums/Industry.java index 434aa7d..a981f98 100644 --- a/src/main/java/DiffLens/back_end/domain/members/enums/Industry.java +++ b/src/main/java/DiffLens/back_end/domain/members/enums/Industry.java @@ -1,10 +1,28 @@ package DiffLens.back_end.domain.members.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** * 업종 */ +@Getter +@AllArgsConstructor public enum Industry { - IT, FINANCE, EDUCATION + IT_SOFTWARE("IT·인터넷·소프트웨어"), + ELECTRONICS_MANUFACTURING("전자·제조·기계"), + FINANCE_INSURANCE("금융·보험·핀테크"), + DISTRIBUTION_FOOD("유통·소비재·식품"), + CULTURE_MEDIA("문화·미디어·엔터테인먼트"), + MEDICAL_BIO("의료·제약·바이오"), + EDUCATION_EDUTECH("교육·에듀테크"), + PUBLIC_ADMIN("공공·비영리·행정"), + CONSTRUCTION_INFRA("건설·부동산·인프라"), + ENERGY_ENVIRONMENT("에너지·환경·화학"), + TOURISM_TRAVEL("관광·여행·항공"), + ETC("기타 산업군"); + private final String krValue; } + diff --git a/src/main/java/DiffLens/back_end/domain/members/enums/Job.java b/src/main/java/DiffLens/back_end/domain/members/enums/Job.java index 833e9f7..b761f14 100644 --- a/src/main/java/DiffLens/back_end/domain/members/enums/Job.java +++ b/src/main/java/DiffLens/back_end/domain/members/enums/Job.java @@ -1,10 +1,29 @@ package DiffLens.back_end.domain.members.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** * 직무 */ +@Getter +@AllArgsConstructor public enum Job { - ETC + MANAGEMENT("경영/기획/전략"), + MARKETING("마케팅/광고/홍보"), + SALES("영업/고객관리"), + IT("IT/개발/데이터"), + DESIGN("디자인/미디어"), + PRODUCTION("생산/제조/품질"), + RESEARCH("연구/R&D"), + EDUCATION("교육/컨설팅"), + MEDICAL("의료/보건/복지"), + FINANCE("금융/회계/법률"), + SERVICE("서비스/유통"), + ETC_FREELANCER("기타/프리랜서") + ; + + private final String krValue; } diff --git a/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java b/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java index 878ec4f..a68e5f7 100644 --- a/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java +++ b/src/main/java/DiffLens/back_end/domain/members/enums/PlanEnum.java @@ -3,7 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; -@Getter1 +@Getter @AllArgsConstructor public enum PlanEnum { diff --git a/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java index d4613a1..19a67f6 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/MemberInitializer.java @@ -2,6 +2,8 @@ import DiffLens.back_end.domain.members.auth.strategy.implement.AuthAdminStrategy; import DiffLens.back_end.domain.members.dto.auth.AuthRequestDTO; +import DiffLens.back_end.domain.members.enums.Industry; +import DiffLens.back_end.domain.members.enums.Job; import DiffLens.back_end.domain.members.enums.LoginType; import DiffLens.back_end.domain.members.enums.PlanEnum; import DiffLens.back_end.domain.members.repository.MemberRepository; @@ -33,7 +35,9 @@ public void initialize() { "admin", PASSWORD, LoginType.GENERAL, - PlanEnum.BUSINESS + PlanEnum.BUSINESS, + Job.ETC_FREELANCER, + Industry.ETC ); authStrategy.signUp(admin); } From a6c9c671e32638b0a357376b6d4f6772aac321dc Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 23 Oct 2025 02:00:25 +0900 Subject: [PATCH 18/32] =?UTF-8?q?feat=20:=20=EA=B2=80=EC=83=89=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=97=90=20=EC=9D=BC=EC=B9=98=EC=9C=A8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=20=20-=20=EA=B2=80=EC=83=89=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=8B=9C=20=EA=B2=80=EC=83=89=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=97=90=20=EC=9D=BC=EC=B9=98=EC=9C=A8=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84=20=20=20-=20Se?= =?UTF-8?q?archHistory=EC=97=90=20=EC=9D=BC=EC=B9=98=EC=9C=A8(float[])=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/panel/dto/fast/FastPanelResponseDTO.java | 3 ++- .../back_end/domain/search/entity/SearchHistory.java | 4 ++++ .../search/service/implement/NaturalSearchService.java | 3 ++- .../search/service/implement/SearchHistoryServiceImpl.java | 7 ++++--- .../search/service/interfaces/SearchHistoryService.java | 3 ++- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java index 5c02201..4df7249 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.panel.dto.fast; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -16,7 +17,7 @@ public static class SearchFastResult { // 일반적인 데이터 private List panelList; - private Float accuracy; // 일치율 + private List accuracyList; // 일치율 // 차트 관련 private List charts; diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java index 4cb9c39..0ec1006 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java @@ -31,6 +31,10 @@ public class SearchHistory extends BaseEntity { @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") private List panelIds; + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(nullable = false, columnDefinition = "float[] DEFAULT ") + private List concordanceRate; // 일치율 + // 연관관계 @OneToOne(cascade = CascadeType.REMOVE, mappedBy = "searchHistory") diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index ea22576..f71a84f 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -50,7 +50,7 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re // DB 에서 Panel 목록 가져옴 List foundPanels = panelRepository.findByIdList(panelIdList); // SearchHistory 데이터 생성 및 저장 - SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, panelIdList); + SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, response); // SearchResult.Summary 생성 SearchResponseDTO.SearchResult.Summary summary = summaryConverter.requestToDto(response, foundPanels); @@ -60,6 +60,7 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re // 차트 생성 List charts = chartService.makeChart(response, searchHistory, foundPanels); charts.forEach(searchHistory::addChart); // 연관관계 편의 메서드 호출 + // TODO : 아직 차트가 저장되지는 않을거임 // 차트 변환 List graphs = chartConverter.requestToDto(null, charts); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index 829a98d..d52c789 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.service.implement; +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.entity.SearchFilter; import DiffLens.back_end.domain.search.entity.SearchHistory; @@ -10,7 +11,6 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; -import java.util.List; @Service @RequiredArgsConstructor @@ -20,14 +20,15 @@ public class SearchHistoryServiceImpl implements SearchHistoryService { private final FilterService filterService; @Override - public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, List panelIdList) { + public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse) { // SearchHistory 데이터 생성 및 저장 SearchHistory searchHistory = historyRepository.save( SearchHistory.builder() .date(LocalDate.now()) .content(request.getQuestion()) - .panelIds(panelIdList) + .panelIds(fastApiResponse.getPanelList()) + .concordanceRate(fastApiResponse.getAccuracyList()) .build() ); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java index b70aef2..537d4b8 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java @@ -1,10 +1,11 @@ package DiffLens.back_end.domain.search.service.interfaces; +import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.entity.SearchHistory; import java.util.List; public interface SearchHistoryService { - SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, List panelIdList); + SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse); } From 0ae4127d5836e3885a45c7e9568ae835453e5a9e Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 23 Oct 2025 02:04:18 +0900 Subject: [PATCH 19/32] =?UTF-8?q?feat=20:=20offset=20limit=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?dto=20=EC=B6=94=EA=B0=80=20=20=20-=20=EC=BB=A4=EC=84=9C?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=ED=8E=98=EC=9D=B4=EC=A7=95=20dto=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/library/dto/LibraryResponseDTO.java | 2 +- .../domain/library/service/LibraryService.java | 2 +- .../domain/search/dto/SearchPanelDTO.java | 2 +- .../back_end/global/dto/ResponsePageDTO.java | 17 ++++++++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index 3632e8c..7b7909b 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -21,7 +21,7 @@ public static class ListResult { private List libraries; @JsonProperty("page_info") - private ResponsePageDTO.PageInfo pageInfo; + private ResponsePageDTO.CursorPageInfo cursorPageInfo; @Getter @Builder diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index 61cf881..8c30ab8 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -152,7 +152,7 @@ public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { return LibraryResponseDTO.ListResult.builder() .libraries(libraryItems) - .pageInfo(null) // 페이징이 필요하면 추후 구현 + .cursorPageInfo(null) // 페이징이 필요하면 추후 구현 .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchPanelDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchPanelDTO.java index cc49748..e99c256 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchPanelDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchPanelDTO.java @@ -20,7 +20,7 @@ public static class PanelData { private List records; @JsonProperty("page_info") - private ResponsePageDTO.PageInfo pageInfo; + private ResponsePageDTO.CursorPageInfo cursorPageInfo; } @Getter diff --git a/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java b/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java index 8850626..442336d 100644 --- a/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java +++ b/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java @@ -12,7 +12,7 @@ public class ResponsePageDTO { @Builder @AllArgsConstructor @NoArgsConstructor - public static class PageInfo { + public static class CursorPageInfo { @JsonProperty("next_cursor") private Integer nextCursor; @@ -25,4 +25,19 @@ public static class PageInfo { private Integer currentPageCount; } + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class OffsetLimitPageInfo { + + private Integer offset; + + private Integer page; + + @JsonProperty("current_page_count") + private Integer currentPageCount; + + } + } From 651e5ed9666c79eb82690f19eb9727dcefc8f3e9 Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 23 Oct 2025 05:40:24 +0900 Subject: [PATCH 20/32] =?UTF-8?q?feat=20:=20=EA=B0=9C=EB=B3=84=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20API=20=EA=B5=AC=ED=98=84=20=20=20-=20=EB=85=B8?= =?UTF-8?q?=EC=85=98=EC=97=90=20=EB=82=98=EC=99=80=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=EB=8C=80=EB=A1=9C=20=EB=84=A3=EC=9D=8C=20=20?= =?UTF-8?q?=20-=20=EC=9D=91=EB=8B=B5=20=ED=98=95=ED=83=9C=20=EC=9E=AC?= =?UTF-8?q?=EA=B5=AC=EC=84=B1=20=20=20-=20=EA=B0=9C=EB=B3=84=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20API=20=EA=B5=AC=ED=98=84=20=20=20-=20api=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/dto/fast/FastPanelResponseDTO.java | 2 + .../panel/repository/PanelRepository.java | 4 + .../domain/rawData/converter/JsonToDTO.java | 2 + .../rawData/repository/RawDataRepository.java | 13 +-- .../rawData/service/RawDataService.java | 6 ++ .../rawData/service/RawDataServiceImpl.java | 51 +++++++++++- .../search/controller/SearchController.java | 37 ++++++++- .../domain/search/dto/SearchResponseDTO.java | 45 +++++++++-- .../repository/SearchHistoryRepository.java | 2 + .../implement/NaturalSearchService.java | 2 +- .../implement/SearchHistoryServiceImpl.java | 79 +++++++++++++++++++ .../interfaces/SearchHistoryService.java | 4 +- .../back_end/global/dto/ResponsePageDTO.java | 31 +++++++- .../code/status/error/ErrorStatus.java | 7 +- .../code/status/error/SearchStatus.java | 30 +++++++ 15 files changed, 292 insertions(+), 23 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java index 4df7249..8733c37 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java @@ -16,6 +16,8 @@ public class FastPanelResponseDTO { public static class SearchFastResult { // 일반적인 데이터 + private Float accuracy; + private List panelList; private List accuracyList; // 일치율 diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java index 7255a86..be77f5d 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java @@ -1,6 +1,8 @@ package DiffLens.back_end.domain.panel.repository; import DiffLens.back_end.domain.panel.entity.Panel; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,4 +19,6 @@ public interface PanelRepository extends JpaRepository { ) public List findByIdList(@Param("ids") List ids); + Page findByIdIn(List ids, Pageable pageable); + } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java index 2715b54..1ae3e29 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/converter/JsonToDTO.java @@ -4,9 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import org.springframework.stereotype.Component; @AllArgsConstructor @NoArgsConstructor +@Component public class JsonToDTO implements RawDataConverter { private ObjectMapper objectMapper = new ObjectMapper(); diff --git a/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java b/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java index c9c09e4..67a9796 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/repository/RawDataRepository.java @@ -1,14 +1,17 @@ package DiffLens.back_end.domain.rawData.repository; import DiffLens.back_end.domain.rawData.entity.RawData; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; + +import java.util.List; public interface RawDataRepository extends JpaRepository { - @Query("delete from RawData") - @Modifying - void deleteAllRawData(); + + List findAllByIdIn(List panelIds); + + Page findAllByIdIn(List panelIds, Pageable pageable); } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java index 35ac6e1..8b10545 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataService.java @@ -1,7 +1,13 @@ package DiffLens.back_end.domain.rawData.service; import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; public interface RawDataService { PanelDTO getRawDataDTO(String panelId); + List getRawDataDTOList(List panelId); + Page getRawDataDTOList(List panelId, Pageable pageable); } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java index a394928..b62ce9d 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataServiceImpl.java @@ -1,6 +1,5 @@ package DiffLens.back_end.domain.rawData.service; -import DiffLens.back_end.domain.rawData.converter.JsonToDTO; import DiffLens.back_end.domain.rawData.converter.RawDataConverter; import DiffLens.back_end.domain.rawData.dto.PanelDTO; import DiffLens.back_end.domain.rawData.entity.RawData; @@ -8,14 +7,20 @@ import DiffLens.back_end.global.responses.code.status.error.RawDataStatus; import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor public class RawDataServiceImpl implements RawDataService { private final RawDataRepository rawDataRepository; + private final RawDataConverter rawDataConverter; /** * @@ -32,8 +37,50 @@ public PanelDTO getRawDataDTO(String panelId) { .orElseThrow(() -> new ErrorHandler(RawDataStatus.RAW_DATA_NOT_FOUND)); Object json = rawData.getJson(); - RawDataConverter rawDataConverter = new JsonToDTO(); return rawDataConverter.convert(json); } + + /** + * + * panelId 목록으로 List를 반환합니다. + * + */ + @Override + @Transactional(readOnly = true) + public List getRawDataDTOList(List panelIds) { + List rawDataList = rawDataRepository.findAllByIdIn(panelIds); + return getPanelDTOS(rawDataList); + } + + /** + * + * panelId 목록으로 Page를 반환합니다. + * + */ + @Override + @Transactional(readOnly = true) + public Page getRawDataDTOList(List panelIds, Pageable pageable) { + Page rawDataPage = rawDataRepository.findAllByIdIn(panelIds, pageable); + List panelDTOList = getPanelDTOS(rawDataPage.getContent()); + return new PageImpl<>(panelDTOList, pageable, rawDataPage.getTotalElements()); + } + + /** + * + * List 목록을 List로 변환하여 반환합니다. + * + * @param rawDataList + * @return + */ + private List getPanelDTOS(List rawDataList) { + if (rawDataList.isEmpty()) { + throw new ErrorHandler(RawDataStatus.RAW_DATA_NOT_FOUND); + } + return rawDataList.stream() + .map(RawData::getJson) + .map(rawDataConverter::convert) + .toList(); + } + } diff --git a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java index f12d08c..4d29107 100644 --- a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java +++ b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java @@ -2,6 +2,7 @@ import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import DiffLens.back_end.domain.search.service.interfaces.SearchHistoryService; import DiffLens.back_end.domain.search.service.interfaces.SearchService; import DiffLens.back_end.global.responses.exception.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -18,9 +19,22 @@ public class SearchController { private final SearchService naturalServiceService; private final SearchService existingSearchService; + private final SearchHistoryService searchHistoryService; @PostMapping - @Operation(summary = "자연어 검색 ( 미구현 )") + @Operation(summary = "자연어 검색 ( 부분개발, 테스트 전 )", + description = """ + + ## 개요 + 자연어 검색 API 입니다. + + ## request body + - 검색 모드와 필터 항목들은 노션에 정리하여 올리겠습니다. + + ## 응답 + 검색 결과에 개별응답은 포함하지 않았습니다. 개별 API를 조회해야 합니다. + + """) public ApiResponse naturalLanguage(@RequestBody @Valid SearchRequestDTO.NaturalLanguage request) { SearchResponseDTO.SearchResult result = naturalServiceService.search(request); return ApiResponse.onSuccess(result); @@ -34,9 +48,24 @@ public ApiResponse refine(@RequestBody @Valid Se } @GetMapping("/{searchId}/each-responses") - @Operation(summary = "개별 응답 데이터 ( 미구현 )") - public ApiResponse eachResponses(@PathVariable("searchId") Long searchId, Integer pageNum, Integer size){ - SearchResponseDTO.EachResponses result = new SearchResponseDTO.EachResponses(); + @Operation(summary = "개별 응답 데이터 ( 테스트 전 )", + description = """ + + ## 개요 + 개별 응답 데이터 조회 API 입니다. + 페이징 문제로 인해 검색 API와 분리하였습니다. + + ## 요청값 + - searchId : 검색결과 ID. 검색 API 에서 받은 식별자 값(searchId)를 넣으면 됩니다. + - page : 페이지 번호입니다. 1부터 시작입니다. + - size : 한 페이지 크기입니다. + + ## 응답 + 현재 피그마에 나와있는대로 구현했습니다. + + """) + public ApiResponse eachResponses(@PathVariable("searchId") Long searchId, @RequestParam("page") Integer page, @RequestParam("size") Integer size){ + SearchResponseDTO.EachResponses result = searchHistoryService.getEachResponses(searchId, page, size); return ApiResponse.onSuccess(result); } diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java index 1eadf84..aecfd0f 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java @@ -1,5 +1,7 @@ package DiffLens.back_end.domain.search.dto; +import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import DiffLens.back_end.global.dto.ResponsePageDTO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; @@ -27,8 +29,8 @@ public static class SearchResult { private List charts; - @JsonProperty("panel_data") - private SearchPanelDTO.PanelData panelData; +// @JsonProperty("panel_data") // 개별 API로 분리 +// private SearchPanelDTO.PanelData panelData; // 중간배열 @Getter @@ -70,12 +72,43 @@ public static class AppliedFilter { @NoArgsConstructor public static class EachResponses { - private List keys; - private List> values; - private Integer page; - private Integer size; + private List keys; // columns + private List values; // row에 들어가는 값 목록 + + @JsonProperty("page_info") + private ResponsePageDTO.OffsetLimitPageInfo pageInfo; } + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class ResponseValues{ + @JsonProperty("respondent_id") + private String respondentId; + private String gender; + private String age; + private String residence; + @JsonProperty("personal_income") + private String personalIncome; + @JsonProperty("concordance_rate") + private String concordanceRate; // 일치율 + + public static ResponseValues fromPanelDTO(PanelDTO panelDTO, String concordanceRate) { + return ResponseValues.builder() + .respondentId(panelDTO.getPanelId()) + .gender(panelDTO.getGender()) + .age(panelDTO.getAge().toString()) + .residence(panelDTO.getResidence()) + .personalIncome(panelDTO.getPersonalIncome()) + .concordanceRate(concordanceRate) + .build(); + } + + } + + + } diff --git a/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java b/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java index abcc174..df9e2f4 100644 --- a/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java +++ b/src/main/java/DiffLens/back_end/domain/search/repository/SearchHistoryRepository.java @@ -3,5 +3,7 @@ import DiffLens.back_end.domain.search.entity.SearchHistory; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface SearchHistoryRepository extends JpaRepository { } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index f71a84f..8fa310a 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -73,7 +73,7 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re .summary(summary) .appliedFiltersSummary(appliedFiltersSummary) .charts(graphs) - .panelData(panelData) +// .panelData(panelData) // 개별 API로 분리 .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index d52c789..07779b2 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,24 +1,38 @@ package DiffLens.back_end.domain.search.service.implement; import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import DiffLens.back_end.domain.rawData.service.RawDataService; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import DiffLens.back_end.domain.search.entity.SearchFilter; import DiffLens.back_end.domain.search.entity.SearchHistory; import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; import DiffLens.back_end.domain.search.service.interfaces.FilterService; import DiffLens.back_end.domain.search.service.interfaces.SearchHistoryService; +import DiffLens.back_end.global.dto.ResponsePageDTO; +import DiffLens.back_end.global.responses.code.status.error.ErrorStatus; +import DiffLens.back_end.global.responses.code.status.error.SearchStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; 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 java.time.LocalDate; +import java.util.List; @Service @RequiredArgsConstructor public class SearchHistoryServiceImpl implements SearchHistoryService { private final SearchHistoryRepository historyRepository; + private final RawDataService rawDataService; private final FilterService filterService; + private final List keys = List.of("응답자ID", "성별", "나이", "거주지", "월소득", "일치율"); + @Override public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse) { @@ -40,4 +54,69 @@ public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, return searchHistory; } + + /** + * + * 개별 응답 데이터 조회입니다. + * 검색 결과에서 패널 정보와 일치율 정보를 가져와 페이징 처리하여 ( offset, limit 기반 ) 반환합니다. + * 현재 패널 정보 항목은 keys 변수와 같습니다. + * 항목이 바뀐다면 keys와 SearchResponseDTO.ResponseValues의 필드를 수정해야 합니다. + * + * @param searchHistoryId searchHistory의 식별자 + * @param pageNum 페이지 번호. 1 이상의 값 + * @param size 페이지 크기 + * @return EachResponses 객체 + */ + @Override + public SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, Integer pageNum, Integer size) { + + // 페이지 번호 예외처리 + if(pageNum < 1){ + throw new ErrorHandler(ErrorStatus.PAGE_NO_INVALID); + } + // searchHistoryId로 검색기록 불러옴 + SearchHistory searchHistory = historyRepository.findById(searchHistoryId) + .orElseThrow(() -> new ErrorHandler(SearchStatus.SEARCH_HISTORY_NOT_FOUND)); + + // 검색기록에서 패널ID 목록이랑 일치율 목록 불러옴 + // 두 리스트의 순서쌍이 이루어짐 + List panelIds = searchHistory.getPanelIds(); + List concordanceRate = searchHistory.getConcordanceRate(); + + // 페이징을 위한 Pageable 객체 생성 + // 페이지는 1부터 시작 + Pageable pageable = PageRequest.of(pageNum - 1, size); + + // PanelId 목록을 이용해서 원천데이터 DTO ( PanelDTO ) 조회 + Page rawDataDTOList = rawDataService.getRawDataDTOList(panelIds, pageable); + + // 페이지 범위 초과 검사 추가 + if (pageNum > rawDataDTOList.getTotalPages() && rawDataDTOList.getTotalPages() > 0) { + throw new ErrorHandler(ErrorStatus.PAGE_NO_EXCEED); + } + + // PanelDTO 목록 순회하며 응답보낼 데이터 파싱 + // panelIds와 concordanceRate의 순서쌍에 맞게 패널 정보와 일치율을 담음 + List values = rawDataDTOList.stream() + .map(panelDTO -> { + int index = panelIds.indexOf(panelDTO.getPanelId()); // + String rate = (index != -1 && index < concordanceRate.size()) + ? String.format("%.2f", concordanceRate.get(index)) : null; + + return SearchResponseDTO.ResponseValues.fromPanelDTO(panelDTO, rate); + }) + .toList(); + + //페이징 정보 생성 + ResponsePageDTO.OffsetLimitPageInfo pageInfo = ResponsePageDTO.OffsetLimitPageInfo.from(rawDataDTOList); + + return SearchResponseDTO.EachResponses.builder() + .keys(keys) + .values(values) + .pageInfo(pageInfo) + .build(); + } + + + } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java index 537d4b8..ce2a9b5 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java @@ -2,10 +2,10 @@ import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; +import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import DiffLens.back_end.domain.search.entity.SearchHistory; -import java.util.List; - public interface SearchHistoryService { SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse); + SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, Integer pageNum, Integer size); } diff --git a/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java b/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java index 442336d..341d283 100644 --- a/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java +++ b/src/main/java/DiffLens/back_end/global/dto/ResponsePageDTO.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.data.domain.Page; public class ResponsePageDTO { @@ -33,11 +34,39 @@ public static class OffsetLimitPageInfo { private Integer offset; - private Integer page; + @JsonProperty("current_page") + private Integer currentPage; @JsonProperty("current_page_count") private Integer currentPageCount; + @JsonProperty("total_page_count") + private Integer totalPageCount; + + private Integer limit; + + @JsonProperty("total_count") + private Long totalCount; + + @JsonProperty("has_next") + private Boolean hasNext; + + @JsonProperty("has_previous") + private Boolean hasPrevious; + + public static OffsetLimitPageInfo from(Page page) { + return OffsetLimitPageInfo.builder() + .offset(page.getPageable().getPageNumber() * page.getPageable().getPageSize()) + .currentPage(page.getNumber() + 1) + .currentPageCount(page.getNumberOfElements()) + .totalPageCount(page.getTotalPages()) + .limit(page.getSize()) + .totalCount(page.getTotalElements()) + .hasNext(page.hasNext()) + .hasPrevious(page.hasPrevious()) + .build(); + } + } } diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/ErrorStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/ErrorStatus.java index f730c74..9eb922b 100644 --- a/src/main/java/DiffLens/back_end/global/responses/code/status/error/ErrorStatus.java +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/ErrorStatus.java @@ -15,8 +15,11 @@ public enum ErrorStatus implements BaseErrorCode { BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), - INVALID_INPUT(HttpStatus.BAD_REQUEST, "COMMON4001", "입력값이 유효하지 않습니다."), - INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "COMMON4002", "code 누락 또는 공백 등 잘못된 파라미터입니다."), + INVALID_INPUT(HttpStatus.BAD_REQUEST, "COMMON404", "입력값이 유효하지 않습니다."), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "COMMON405", "code 누락 또는 공백 등 잘못된 파라미터입니다."), + PAGE_NO_INVALID(HttpStatus.BAD_REQUEST, "COMMON406", "페이지 번호는 1 이상 이어야 합니다."), + PAGE_NO_EXCEED(HttpStatus.BAD_REQUEST, "COMMON407", "최대 페이지를 벗어났습니다."), + ; diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java new file mode 100644 index 0000000..751495d --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java @@ -0,0 +1,30 @@ +package DiffLens.back_end.global.responses.code.status.error; + +import DiffLens.back_end.global.responses.code.BaseErrorCode; +import DiffLens.back_end.global.responses.code.ErrorReasonDto; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SearchStatus implements BaseErrorCode { + + SEARCH_HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH404", "검색기록을 찾을 수 없습니다.") + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + + @Override + public ErrorReasonDto getReason() { + return null; + } + + @Override + public ErrorReasonDto getReasonHttpStatus() { + return null; + } +} From ffc659fbd80f234c93957f042477380ce0430d54 Mon Sep 17 00:00:00 2001 From: junyong Date: Thu, 23 Oct 2025 06:01:00 +0900 Subject: [PATCH 21/32] =?UTF-8?q?feat=20:=20=EC=9E=90=EC=97=B0=EC=96=B4=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20dto=EC=97=90=EC=84=9C=20=EC=A7=88=EB=AC=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=93=9C=20=ED=95=84=EB=93=9C=20string=20->=20enum?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/search/dto/SearchRequestDTO.java | 3 ++- .../domain/search/enums/mode/QuestionMode.java | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/main/java/DiffLens/back_end/domain/search/enums/mode/QuestionMode.java diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java index 6ae58f0..7c189c7 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.dto; +import DiffLens.back_end.domain.search.enums.mode.QuestionMode; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -16,7 +17,7 @@ public static class NaturalLanguage{ @NotBlank private String question; @NotBlank - private String mode; + private QuestionMode mode; @NotNull private SearchFilters filters; } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/mode/QuestionMode.java b/src/main/java/DiffLens/back_end/domain/search/enums/mode/QuestionMode.java new file mode 100644 index 0000000..7d18485 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/enums/mode/QuestionMode.java @@ -0,0 +1,15 @@ +package DiffLens.back_end.domain.search.enums.mode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum QuestionMode { + + FLEXIBLE("유연모드"), + STRICT("엄격모드"); + + private final String kr; + +} \ No newline at end of file From 50976abadbbd6cfac9892d70e0e1eabde0b72aef Mon Sep 17 00:00:00 2001 From: hardwoong Date: Thu, 23 Oct 2025 10:59:00 +0900 Subject: [PATCH 22/32] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/controller/PanelController.java | 25 ++++-- .../domain/panel/dto/PanelResponseDTO.java | 20 ----- .../domain/panel/service/PanelService.java | 78 +++++++++++++++++++ .../data/initializer/FilterInitializer.java | 4 +- 4 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/controller/PanelController.java b/src/main/java/DiffLens/back_end/domain/panel/controller/PanelController.java index 3f16a43..b0c6373 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/controller/PanelController.java +++ b/src/main/java/DiffLens/back_end/domain/panel/controller/PanelController.java @@ -2,6 +2,7 @@ import DiffLens.back_end.domain.panel.dto.PanelRequestDTO; import DiffLens.back_end.domain.panel.dto.PanelResponseDTO; +import DiffLens.back_end.domain.panel.service.PanelService; import DiffLens.back_end.global.responses.exception.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,18 +16,32 @@ @RequiredArgsConstructor public class PanelController { + private final PanelService panelService; + @GetMapping("/{panelId}") - @Operation(summary = "특정 패널 상세 조회 ( 미구현 )") - public ApiResponse details(@PathVariable("panelId") Long panelId) { - PanelResponseDTO.PanelDetails result = new PanelResponseDTO.PanelDetails(); + @Operation(summary = "특정 패널 상세 조회", description = """ + ## 개요 + 특정 패널의 상세 정보를 조회하는 API입니다. + + ## 응답 데이터 + - 패널의 기본 정보 (성별, 나이, 거주지 등) + - 패널의 속성 정보 (기기, 해시태그 등) + - 연결된 RawData 정보 + + ## 권한 + 인증된 사용자만 접근 가능합니다. + """) + public ApiResponse details(@PathVariable("panelId") String panelId) { + PanelResponseDTO.PanelDetails result = panelService.getPanelDetails(panelId); return ApiResponse.onSuccess(result); } @PostMapping("/compare") @Operation(summary = "패널 그룹 비교 분석 ( 미구현 ) ") - public ApiResponse groupCompare(@RequestBody @Valid PanelRequestDTO.GroupAnal request) { + public ApiResponse groupCompare( + @RequestBody @Valid PanelRequestDTO.GroupAnal request) { PanelResponseDTO.GroupCompare result = new PanelResponseDTO.GroupCompare(); - return ApiResponse.onSuccess(result); + return ApiResponse.onSuccess(result); } } diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java index dd3ad19..cd15826 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java @@ -19,9 +19,6 @@ public static class PanelDetails { @JsonProperty("panel_detail") private PanelDetail panelDetail; - @JsonProperty("similar_panels") - private List similarPanels; - @Getter @Builder @NoArgsConstructor @@ -73,23 +70,6 @@ public static class Attribute { } } - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class SimilarPanel { - - @JsonProperty("panel_id") - private String panelId; - - @JsonProperty("display_name") - private String displayName; - - private List tags; - - @JsonProperty("match_score") - private Integer matchScore; - } } @Getter diff --git a/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java b/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java new file mode 100644 index 0000000..8ada2bb --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java @@ -0,0 +1,78 @@ +package DiffLens.back_end.domain.panel.service; + +import DiffLens.back_end.domain.panel.dto.PanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.rawData.entity.RawData; +import DiffLens.back_end.global.responses.code.status.error.ErrorStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PanelService { + + private final PanelRepository panelRepository; + + @Transactional(readOnly = true) + public PanelResponseDTO.PanelDetails getPanelDetails(String panelId) { + // 1. 패널 조회 + Panel panel = panelRepository.findById(panelId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + // 2. RawData 조회 (OneToOne 관계로 자동 로딩) + RawData rawData = panel.getRawData(); + + // 3. PanelDetail 생성 + PanelResponseDTO.PanelDetails.PanelDetail panelDetail = PanelResponseDTO.PanelDetails.PanelDetail.builder() + .panelId(panel.getId()) + .summary(panel.getProfileSummary()) + .basicInfo(PanelResponseDTO.PanelDetails.PanelDetail.BasicInfo.builder() + .gender(panel.getGender() != null ? panel.getGender().toString() : null) + .residence(panel.getResidence()) + .maritalStatus(panel.getMartialStatus()) + .childrenCount(panel.getChildrenCount()) + .occupation(panel.getOccupation()) + .build()) + .attributes(createAttributes(panel, rawData)) + .build(); + + return PanelResponseDTO.PanelDetails.builder() + .panelDetail(panelDetail) + .build(); + } + + private List createAttributes(Panel panel, RawData rawData) { + return List.of( + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("age_group") + .value(panel.getAgeGroup()) + // .unit("대") + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("education") + .value(panel.getEducation()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("devices") + .value(panel.getDevices().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("hash_tags") + .value(panel.getHashTags().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("raw_data") + .value(rawData != null ? rawData.getJson().toString() : "No raw data available") + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("embedding_dimension") + .value(panel.getEmbedding() != null ? String.valueOf(panel.getEmbedding().length) + : "No embedding") + .build()); + } +} diff --git a/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java b/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java index 7b1a967..da37139 100644 --- a/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java +++ b/src/main/java/DiffLens/back_end/global/util/data/initializer/FilterInitializer.java @@ -105,7 +105,9 @@ private String getDisplayDataValue(Enum enumValue) { private String getRawDataValue(Enum enumValue) { try { Method valueMethod = enumValue.getClass().getMethod("getRawDataValue"); - return (String) valueMethod.invoke(enumValue); + String rawDataValue = (String) valueMethod.invoke(enumValue); + // null 값인 경우 기본값 설정 + return rawDataValue != null ? rawDataValue : "UNKNOWN"; } catch (Exception e) { throw new RuntimeException("Enum RawDataValue 조회 실패: " + enumValue.name(), e); } From 6a46f3e4bf747d3f4ee029d9db6272918e144a8e Mon Sep 17 00:00:00 2001 From: hardwoong Date: Thu, 23 Oct 2025 13:59:20 +0900 Subject: [PATCH 23/32] =?UTF-8?q?feat:=20=ED=8C=A8=EB=84=90=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=ED=8A=B9=EC=A0=95=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=93=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=9D=91=EB=8B=B5=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/panel/dto/PanelResponseDTO.java | 5 - .../back_end/domain/panel/entity/Panel.java | 55 ++++- .../domain/panel/service/PanelService.java | 190 +++++++++++++----- 3 files changed, 188 insertions(+), 62 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java index cd15826..f02aae5 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/panel/dto/PanelResponseDTO.java @@ -66,7 +66,6 @@ public static class BasicInfo { public static class Attribute { private String key; private String value; // 문자열 or 배열 모두 가능 - private String unit; } } @@ -115,8 +114,6 @@ public static class FeatureComparison { @JsonProperty("b_value") private Double bValue; - - private String unit; } @Getter @@ -164,8 +161,6 @@ public static class BasicComparison { @JsonProperty("b_value") private Double bValue; - - private String unit; } } diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java index 06774a5..97b3fc6 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java @@ -26,28 +26,78 @@ public class Panel extends BaseEntity { @Enumerated(EnumType.STRING) private Gender gender; + @Column + @Min(0) + private Integer age; + @Column(length = 20) private String ageGroup; + @Column(length = 4) + private Integer birthYear; + @Column(length = 100) private String residence; @Column(length = 50) - private String martialStatus; + private String maritalStatus; @Column @Min(0) private Integer childrenCount; + @Column(length = 10) + private String familySize; + @Column(length = 50) private String education; @Column(length = 200) private String occupation; + @Column(length = 100) + private String job; + + @Column(length = 50) + private String personalIncome; + + @Column(length = 50) + private String householdIncome; + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") + private List electronicDevices = new ArrayList<>(); + + @Column(length = 100) + private String phoneBrand; + + @Column(length = 100) + private String phoneModel; + + @Column(length = 20) + private String carOwnership; + + @Column(length = 50) + private String carBrand; + + @Column(length = 100) + private String carModel; + @JdbcTypeCode(SqlTypes.ARRAY) @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") - private List devices = new ArrayList<>(); + private List smokingExperience = new ArrayList<>(); + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") + private List cigaretteBrands = new ArrayList<>(); + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") + private List eCigarette = new ArrayList<>(); + + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(columnDefinition = "text[] DEFAULT '{}'::text[]") + private List drinkingExperience = new ArrayList<>(); @Column(columnDefinition = "TEXT") private String profileSummary; @@ -66,5 +116,4 @@ public class Panel extends BaseEntity { @MapsId private RawData rawData; - } diff --git a/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java b/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java index 8ada2bb..382a4d7 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java +++ b/src/main/java/DiffLens/back_end/domain/panel/service/PanelService.java @@ -16,63 +16,145 @@ @RequiredArgsConstructor public class PanelService { - private final PanelRepository panelRepository; + private final PanelRepository panelRepository; - @Transactional(readOnly = true) - public PanelResponseDTO.PanelDetails getPanelDetails(String panelId) { - // 1. 패널 조회 - Panel panel = panelRepository.findById(panelId) - .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + @Transactional(readOnly = true) + public PanelResponseDTO.PanelDetails getPanelDetails(String panelId) { + // 1. 패널 조회 + Panel panel = panelRepository.findById(panelId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); - // 2. RawData 조회 (OneToOne 관계로 자동 로딩) - RawData rawData = panel.getRawData(); + // 2. RawData 조회 (OneToOne 관계로 자동 로딩) + RawData rawData = panel.getRawData(); - // 3. PanelDetail 생성 - PanelResponseDTO.PanelDetails.PanelDetail panelDetail = PanelResponseDTO.PanelDetails.PanelDetail.builder() - .panelId(panel.getId()) - .summary(panel.getProfileSummary()) - .basicInfo(PanelResponseDTO.PanelDetails.PanelDetail.BasicInfo.builder() - .gender(panel.getGender() != null ? panel.getGender().toString() : null) - .residence(panel.getResidence()) - .maritalStatus(panel.getMartialStatus()) - .childrenCount(panel.getChildrenCount()) - .occupation(panel.getOccupation()) - .build()) - .attributes(createAttributes(panel, rawData)) - .build(); + // 3. PanelDetail 생성 + PanelResponseDTO.PanelDetails.PanelDetail panelDetail = PanelResponseDTO.PanelDetails.PanelDetail + .builder() + .panelId(panel.getId()) + .summary(panel.getProfileSummary()) + .basicInfo(PanelResponseDTO.PanelDetails.PanelDetail.BasicInfo.builder() + .gender(panel.getGender() != null ? panel.getGender().toString() : null) + .residence(panel.getResidence()) + .maritalStatus(panel.getMaritalStatus()) + .childrenCount(panel.getChildrenCount()) + .occupation(panel.getOccupation()) + .build()) + .attributes(createAttributes(panel, rawData)) + .build(); - return PanelResponseDTO.PanelDetails.builder() - .panelDetail(panelDetail) - .build(); - } + return PanelResponseDTO.PanelDetails.builder() + .panelDetail(panelDetail) + .build(); + } - private List createAttributes(Panel panel, RawData rawData) { - return List.of( - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("age_group") - .value(panel.getAgeGroup()) - // .unit("대") - .build(), - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("education") - .value(panel.getEducation()) - .build(), - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("devices") - .value(panel.getDevices().toString()) - .build(), - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("hash_tags") - .value(panel.getHashTags().toString()) - .build(), - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("raw_data") - .value(rawData != null ? rawData.getJson().toString() : "No raw data available") - .build(), - PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() - .key("embedding_dimension") - .value(panel.getEmbedding() != null ? String.valueOf(panel.getEmbedding().length) - : "No embedding") - .build()); - } + private List createAttributes(Panel panel, + RawData rawData) { + return List.of( + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("age") + .value(panel.getAge() != null ? panel.getAge().toString() : null) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("age_group") + .value(panel.getAgeGroup()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("birth_year") + .value(panel.getBirthYear() != null ? panel.getBirthYear().toString() + : null) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("residence") + .value(panel.getResidence()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("marital_status") + .value(panel.getMaritalStatus()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("children_count") + .value(panel.getChildrenCount() != null + ? panel.getChildrenCount().toString() + : null) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("family_size") + .value(panel.getFamilySize()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("education") + .value(panel.getEducation()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("occupation") + .value(panel.getOccupation()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("job") + .value(panel.getJob()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("personal_income") + .value(panel.getPersonalIncome()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("household_income") + .value(panel.getHouseholdIncome()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("electronic_devices") + .value(panel.getElectronicDevices().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("phone_brand") + .value(panel.getPhoneBrand()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("phone_model") + .value(panel.getPhoneModel()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("car_ownership") + .value(panel.getCarOwnership()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("car_brand") + .value(panel.getCarBrand()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("car_model") + .value(panel.getCarModel()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("smoking_experience") + .value(panel.getSmokingExperience().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("cigarette_brands") + .value(panel.getCigaretteBrands().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("e_cigarette") + .value(panel.getECigarette().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("drinking_experience") + .value(panel.getDrinkingExperience().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("hash_tags") + .value(panel.getHashTags().toString()) + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("raw_data") + .value(rawData != null ? rawData.getJson().toString() + : "No raw data available") + .build(), + PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() + .key("embedding_dimension") + .value(panel.getEmbedding() != null + ? String.valueOf(panel.getEmbedding().length) + : "No embedding") + .build()); + } } From ab55e53d4c1ec824f2223014fbf804025906de28 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Oct 2025 01:18:26 +0900 Subject: [PATCH 24/32] =?UTF-8?q?refact=20:=20=EA=B0=9C=EB=B3=84=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20RawData=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20->=20Panel=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/search/dto/SearchResponseDTO.java | 13 +++++----- .../implement/SearchHistoryServiceImpl.java | 21 +++++++++------- .../implement/SearchPanelServiceImpl.java | 24 +++++++++++++++++++ .../interfaces/SearchPanelService.java | 13 ++++++++++ .../code/status/error/SearchStatus.java | 4 +++- 5 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java create mode 100644 src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java index aecfd0f..b5c0536 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.dto; +import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.rawData.dto.PanelDTO; import DiffLens.back_end.global.dto.ResponsePageDTO; import com.fasterxml.jackson.annotation.JsonProperty; @@ -95,13 +96,13 @@ public static class ResponseValues{ @JsonProperty("concordance_rate") private String concordanceRate; // 일치율 - public static ResponseValues fromPanelDTO(PanelDTO panelDTO, String concordanceRate) { + public static ResponseValues fromPanelDTO(Panel panel, String concordanceRate) { return ResponseValues.builder() - .respondentId(panelDTO.getPanelId()) - .gender(panelDTO.getGender()) - .age(panelDTO.getAge().toString()) - .residence(panelDTO.getResidence()) - .personalIncome(panelDTO.getPersonalIncome()) + .respondentId(panel.getId()) + .gender(panel.getGender().getDisplayValue()) + .age("TODO : 나이 넣기") // TODO : Panel에 나이 생기면 넣기 + .residence(panel.getResidence()) + .personalIncome("TODO : 개인소득 넣기") // TODO : Panel에 개인소득 생기면 넣기 .concordanceRate(concordanceRate) .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index 07779b2..884d7a1 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.rawData.dto.PanelDTO; import DiffLens.back_end.domain.rawData.service.RawDataService; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; @@ -10,6 +11,7 @@ import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; import DiffLens.back_end.domain.search.service.interfaces.FilterService; import DiffLens.back_end.domain.search.service.interfaces.SearchHistoryService; +import DiffLens.back_end.domain.search.service.interfaces.SearchPanelService; import DiffLens.back_end.global.dto.ResponsePageDTO; import DiffLens.back_end.global.responses.code.status.error.ErrorStatus; import DiffLens.back_end.global.responses.code.status.error.SearchStatus; @@ -28,6 +30,7 @@ public class SearchHistoryServiceImpl implements SearchHistoryService { private final SearchHistoryRepository historyRepository; + private final SearchPanelService searchPanelService; private final RawDataService rawDataService; private final FilterService filterService; @@ -87,28 +90,28 @@ public SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, In // 페이지는 1부터 시작 Pageable pageable = PageRequest.of(pageNum - 1, size); - // PanelId 목록을 이용해서 원천데이터 DTO ( PanelDTO ) 조회 - Page rawDataDTOList = rawDataService.getRawDataDTOList(panelIds, pageable); + // PanelId 목록을 이용해서 Panel 조회 + Page panelDtoList = searchPanelService.getPanelDtoList(panelIds, pageable); // 페이지 범위 초과 검사 추가 - if (pageNum > rawDataDTOList.getTotalPages() && rawDataDTOList.getTotalPages() > 0) { + if (pageNum > panelDtoList.getTotalPages() && panelDtoList.getTotalPages() > 0) { throw new ErrorHandler(ErrorStatus.PAGE_NO_EXCEED); } - // PanelDTO 목록 순회하며 응답보낼 데이터 파싱 + // Panel 목록 순회하며 응답보낼 데이터 파싱 // panelIds와 concordanceRate의 순서쌍에 맞게 패널 정보와 일치율을 담음 - List values = rawDataDTOList.stream() - .map(panelDTO -> { - int index = panelIds.indexOf(panelDTO.getPanelId()); // + List values = panelDtoList.stream() + .map(panel -> { + int index = panelIds.indexOf(panel.getId()); // String rate = (index != -1 && index < concordanceRate.size()) ? String.format("%.2f", concordanceRate.get(index)) : null; - return SearchResponseDTO.ResponseValues.fromPanelDTO(panelDTO, rate); + return SearchResponseDTO.ResponseValues.fromPanelDTO(panel, rate); }) .toList(); //페이징 정보 생성 - ResponsePageDTO.OffsetLimitPageInfo pageInfo = ResponsePageDTO.OffsetLimitPageInfo.from(rawDataDTOList); + ResponsePageDTO.OffsetLimitPageInfo pageInfo = ResponsePageDTO.OffsetLimitPageInfo.from(panelDtoList); return SearchResponseDTO.EachResponses.builder() .keys(keys) diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java new file mode 100644 index 0000000..66dacea --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java @@ -0,0 +1,24 @@ +package DiffLens.back_end.domain.search.service.implement; + +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.search.service.interfaces.SearchPanelService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SearchPanelServiceImpl implements SearchPanelService { + + private final PanelRepository panelRepository; + + @Override + public Page getPanelDtoList(List panelIds, Pageable pageable) { + return panelRepository.findByIdIn(panelIds, pageable); + } + +} diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java new file mode 100644 index 0000000..34f2145 --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java @@ -0,0 +1,13 @@ +package DiffLens.back_end.domain.search.service.interfaces; + +import DiffLens.back_end.domain.panel.entity.Panel; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface SearchPanelService { + + Page getPanelDtoList(List panelIds, Pageable pageable); + +} diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java index 751495d..9b021bc 100644 --- a/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java @@ -10,7 +10,9 @@ @AllArgsConstructor public enum SearchStatus implements BaseErrorCode { - SEARCH_HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH404", "검색기록을 찾을 수 없습니다.") + SEARCH_HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH404", "검색기록을 찾을 수 없습니다."), + + PANEL_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH405", "패널을 찾을 수 없습니다."), ; private final HttpStatus httpStatus; From 9ae89122f42bcdd6da5a1075331023cf1ac9c974 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Oct 2025 03:32:34 +0900 Subject: [PATCH 25/32] =?UTF-8?q?refact=20:=20=EC=9E=90=EC=97=B0=EC=96=B4?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=EA=B4=80=EB=A0=A8=20fast=20api=20dto?= =?UTF-8?q?=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/dto/fast/FastPanelRequestDTO.java | 6 --- .../search/converter/FilterDtoConverter.java | 6 +-- .../converter/PanelResponseConverter.java | 6 +-- .../search/converter/SummaryDtoConverter.java | 6 +-- .../domain/search/enums/chart/ChartType.java | 5 ++- .../service/implement/ChartServiceImpl.java | 6 +-- .../implement/NaturalSearchService.java | 10 ++--- .../implement/SearchHistoryServiceImpl.java | 5 +-- .../service/interfaces/ChartService.java | 4 +- .../interfaces/SearchHistoryService.java | 4 +- .../fastApi/request/FastPanelRequestDTO.java | 43 +++++++++++++++++++ .../FastNaturalSearchResponseDTO.java} | 17 +++++--- 12 files changed, 81 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java create mode 100644 src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java rename src/main/java/DiffLens/back_end/{domain/panel/dto/fast/FastPanelResponseDTO.java => global/dto/fastApi/response/FastNaturalSearchResponseDTO.java} (75%) diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java deleted file mode 100644 index 0aae021..0000000 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelRequestDTO.java +++ /dev/null @@ -1,6 +0,0 @@ -package DiffLens.back_end.domain.panel.dto.fast; - -public class FastPanelRequestDTO { - - -} diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java index 8559735..4fc6367 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO.SearchResult.AppliedFilter; import DiffLens.back_end.domain.search.entity.Filter; import DiffLens.back_end.domain.search.entity.SearchHistory; @@ -12,12 +12,12 @@ @Component @RequiredArgsConstructor -public class FilterDtoConverter implements SearchDtoConverter, SearchHistory>{ +public class FilterDtoConverter implements SearchDtoConverter, SearchHistory>{ private final FilterService filterService; @Override - public List requestToDto(FastPanelResponseDTO.SearchFastResult response, SearchHistory searchHistory) { + public List requestToDto(FastNaturalSearchResponseDTO.SearchResult response, SearchHistory searchHistory) { List filterIdList = searchHistory.getSearchFilter().getFilters(); List filters = filterService.findFilters(filterIdList); diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java index 4bc3f60..bfcd4ba 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchPanelDTO; import org.springframework.stereotype.Component; @@ -8,9 +8,9 @@ import java.util.List; @Component -public class PanelResponseConverter implements SearchDtoConverter>{ +public class PanelResponseConverter implements SearchDtoConverter>{ @Override - public SearchPanelDTO.PanelData requestToDto(FastPanelResponseDTO.SearchFastResult response, List info) { + public SearchPanelDTO.PanelData requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List info) { // TODO : 개별 응답데이터 처리 로직 작성 return new SearchPanelDTO.PanelData(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java index 2df14f0..1f31db7 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import org.springframework.stereotype.Component; @@ -9,10 +9,10 @@ import java.util.List; @Component -public class SummaryDtoConverter implements SearchDtoConverter> { +public class SummaryDtoConverter implements SearchDtoConverter> { @Override - public SearchResponseDTO.SearchResult.Summary requestToDto(FastPanelResponseDTO.SearchFastResult response, List panelList) { + public SearchResponseDTO.SearchResult.Summary requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List panelList) { return SearchResponseDTO.SearchResult.Summary.builder() .totalRespondents(panelList.size()) diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java b/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java index ed0033f..c38c725 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/chart/ChartType.java @@ -7,7 +7,10 @@ @AllArgsConstructor public enum ChartType { - BAR + BAR, + PIE ; + + } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java index 3b8d215..dd47e55 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.implement; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.panel.util.ReflectionUtil; import DiffLens.back_end.domain.search.entity.Chart; @@ -17,9 +17,9 @@ public class ChartServiceImpl implements ChartService { @Override - public List makeChart(FastPanelResponseDTO.SearchFastResult searchResult, SearchHistory searchHistory, List foundPanels) { + public List makeChart(FastNaturalSearchResponseDTO.SearchResult searchResult, SearchHistory searchHistory, List foundPanels) { - List fastApiChartResponse = searchResult.getCharts(); + List fastApiChartResponse = searchResult.getCharts(); // fast api 로부터 받은 응답값을 List로 변환 List charts = fastApiChartResponse.stream() diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index 8fa310a..d86c0ec 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; import DiffLens.back_end.domain.panel.repository.PanelRepository; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.converter.SearchDtoConverter; import DiffLens.back_end.domain.search.dto.ChartDTO; @@ -32,17 +32,17 @@ public class NaturalSearchService implements SearchService> summaryConverter; - private final SearchDtoConverter, SearchHistory> filterConverter; + private final SearchDtoConverter> summaryConverter; + private final SearchDtoConverter, SearchHistory> filterConverter; private final SearchDtoConverter, List> chartConverter; - private final SearchDtoConverter> panelResponseConverter; + private final SearchDtoConverter> panelResponseConverter; @Override @Transactional(readOnly = false) public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage request) { // TODO : 추후 fast api 에서 불러오도록 수정해야 함 - FastPanelResponseDTO.SearchFastResult response = new FastPanelResponseDTO.SearchFastResult(); + FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); // id List 추출 List panelIdList = response.getPanelList(); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index 884d7a1..6d0f0c2 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,8 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; -import DiffLens.back_end.domain.rawData.dto.PanelDTO; import DiffLens.back_end.domain.rawData.service.RawDataService; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; @@ -37,7 +36,7 @@ public class SearchHistoryServiceImpl implements SearchHistoryService { private final List keys = List.of("응답자ID", "성별", "나이", "거주지", "월소득", "일치율"); @Override - public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse) { + public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastNaturalSearchResponseDTO.SearchResult fastApiResponse) { // SearchHistory 데이터 생성 및 저장 SearchHistory searchHistory = historyRepository.save( diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java index ab4cabc..24b90ed 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.interfaces; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.entity.Chart; import DiffLens.back_end.domain.search.entity.SearchHistory; @@ -8,5 +8,5 @@ import java.util.List; public interface ChartService { - public List makeChart(FastPanelResponseDTO.SearchFastResult fastPanelResponseDTO, SearchHistory searchHistory, List foundPanels); + public List makeChart(FastNaturalSearchResponseDTO.SearchResult fastPanelResponseDTO, SearchHistory searchHistory, List foundPanels); } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java index ce2a9b5..0c786b1 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java @@ -1,11 +1,11 @@ package DiffLens.back_end.domain.search.service.interfaces; -import DiffLens.back_end.domain.panel.dto.fast.FastPanelResponseDTO; +import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import DiffLens.back_end.domain.search.entity.SearchHistory; public interface SearchHistoryService { - SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastPanelResponseDTO.SearchFastResult fastApiResponse); + SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastNaturalSearchResponseDTO.SearchResult fastApiResponse); SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, Integer pageNum, Integer size); } diff --git a/src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java new file mode 100644 index 0000000..440ed62 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java @@ -0,0 +1,43 @@ +package DiffLens.back_end.global.dto.fastApi.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * + * Spring Boot -> Fast API 요청 형태 + * + */ +public class FastPanelRequestDTO { + + // 자연어 검색 요청 + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastNaturalSearch{ + + private String question; + + private String mode; + + private FastSearchFilters filters; + + private static class FastSearchFilters{ + + private Integer count; + private String gender; + private List filters; + + } + + } + + // 재검색 요청 + + +} diff --git a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java b/src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java similarity index 75% rename from src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java rename to src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java index 8733c37..2209442 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/dto/fast/FastPanelResponseDTO.java +++ b/src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java @@ -1,19 +1,25 @@ -package DiffLens.back_end.domain.panel.dto.fast; +package DiffLens.back_end.global.dto.fastApi.response; -import lombok.Builder; import lombok.Getter; import lombok.Setter; import java.util.List; -public class FastPanelResponseDTO { +/** + * + * Fast API -> Spring Boot 응답 형태 + * 자연어 응답 + * + * + */ +public class FastNaturalSearchResponseDTO { /** * fast api에서 패널 식별자 목록을 불러와 db에서 패널 데이터를 불러옵니다. */ @Getter @Setter - public static class SearchFastResult { + public static class SearchResult { // 일반적인 데이터 private Float accuracy; @@ -25,8 +31,7 @@ public static class SearchFastResult { private List charts; // 개별응답 종류 - // TODO : 개별응답 데이터에 어떤 데이터를 넣어야 할지 의논 후 추가 - private List panelColumns; +// private List panelColumns; // SearchResponseDTO.EachResponses로 분리 } From 84b43a3355d684ebb7b92598b332b4acc0e0cd43 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Oct 2025 03:59:07 +0900 Subject: [PATCH 26/32] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B8=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EC=99=80=EC=9D=98=20=EC=97=B0=EB=8F=99=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=A4=80=EB=B9=84=20=20=20-=20http=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=9D=84=20=EB=B3=B4=EB=82=B4=EB=8A=94=20cli?= =?UTF-8?q?ent=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1=20=20=20?= =?UTF-8?q?-=20=EB=82=B4=EB=B6=80=EC=97=90=EC=84=9C=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=9D=84=20=EA=B4=80=EB=A6=AC=ED=95=98=EB=8A=94=20service=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/converter/FilterDtoConverter.java | 2 +- .../converter/PanelResponseConverter.java | 2 +- .../search/converter/SummaryDtoConverter.java | 2 +- .../service/implement/ChartServiceImpl.java | 2 +- .../implement/NaturalSearchService.java | 2 +- .../implement/SearchHistoryServiceImpl.java | 2 +- .../service/interfaces/ChartService.java | 2 +- .../interfaces/SearchHistoryService.java | 2 +- .../global/fastapi/FastApiClient.java | 42 +++++++++++++++++++ .../global/fastapi/FastApiRequestType.java | 20 +++++++++ .../global/fastapi/FastApiService.java | 22 ++++++++++ .../dto}/request/FastPanelRequestDTO.java | 10 ++++- .../FastNaturalSearchResponseDTO.java | 3 +- 13 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java create mode 100644 src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java create mode 100644 src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java rename src/main/java/DiffLens/back_end/global/{dto/fastApi => fastapi/dto}/request/FastPanelRequestDTO.java (79%) rename src/main/java/DiffLens/back_end/global/{dto/fastApi => fastapi/dto}/response/FastNaturalSearchResponseDTO.java (91%) diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java index 4fc6367..988096b 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/FilterDtoConverter.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO.SearchResult.AppliedFilter; import DiffLens.back_end.domain.search.entity.Filter; import DiffLens.back_end.domain.search.entity.SearchHistory; diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java index bfcd4ba..21b4bf0 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchPanelDTO; import org.springframework.stereotype.Component; diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java index 1f31db7..1676c76 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.converter; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import org.springframework.stereotype.Component; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java index dd47e55..1427b7c 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.implement; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.panel.util.ReflectionUtil; import DiffLens.back_end.domain.search.entity.Chart; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index d86c0ec..902d709 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; import DiffLens.back_end.domain.panel.repository.PanelRepository; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.converter.SearchDtoConverter; import DiffLens.back_end.domain.search.dto.ChartDTO; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index 6d0f0c2..c2e5d9c 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.implement; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.rawData.service.RawDataService; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java index 24b90ed..146a7dd 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.interfaces; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.entity.Chart; import DiffLens.back_end.domain.search.entity.SearchHistory; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java index 0c786b1..6d37988 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchHistoryService.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.interfaces; -import DiffLens.back_end.global.dto.fastApi.response.FastNaturalSearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import DiffLens.back_end.domain.search.entity.SearchHistory; diff --git a/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java b/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java new file mode 100644 index 0000000..5a386e1 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java @@ -0,0 +1,42 @@ +package DiffLens.back_end.global.fastapi; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * FastAPI 서버에 실제 HTTP 요청을 보냄 + */ +@Component +@RequiredArgsConstructor +public class FastApiClient { + + private final WebClient webClient = WebClient.builder() + .baseUrl("https://ai.difflens.site") + .build(); + + /** + * + * @param type fast api에 보낼 요청을 관리하는 enum - FastApiRequestType + * @param requestBody fast api에 요청 보낼 클래스의 타입 + * @return fast api 로부터 응답받은 데이터 ( R ) + * @param request body의 클래스 + * @param response body의 클래스 + * + */ + public R sendRequest(FastApiRequestType type, T requestBody) { + + // 요청 타입이 맞지 않을 경우에 대한 예외 + if (!type.getRequestBody().isInstance(requestBody)) { + throw new IllegalArgumentException("올바르지 않은 요청 타입 " + type.name()); // TODO : 에외처리... + } + + return webClient.post() + .uri(type.getUri()) + .bodyValue(requestBody) + .retrieve() + .bodyToMono((Class) type.getResponseType()) + .block(); + } + +} diff --git a/src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java b/src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java new file mode 100644 index 0000000..1efcacc --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java @@ -0,0 +1,20 @@ +package DiffLens.back_end.global.fastapi; + +import DiffLens.back_end.global.fastapi.dto.request.FastPanelRequestDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FastApiRequestType { + + NATURAL_SEARCH("/search/natural", FastPanelRequestDTO.FastNaturalSearch.class, FastNaturalSearchResponseDTO.SearchResult.class), +// REFINE_SEARCH("/search/refine", FastNaturalSearchResponseDTO.SearchResult.class), + ; + + private final String uri; + private final Class requestBody; // request body + private final Class responseType; // response body + +} diff --git a/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java b/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java new file mode 100644 index 0000000..bad2409 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java @@ -0,0 +1,22 @@ +package DiffLens.back_end.global.fastapi; + +import DiffLens.back_end.global.fastapi.dto.request.FastPanelRequestDTO; +import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Fast API 호출 관리 + */ +@Service +@RequiredArgsConstructor +public class FastApiService { + + private final FastApiClient fastApiClient; + + // 자연어 검색 + public FastNaturalSearchResponseDTO.SearchResult getNaturalSearch(FastPanelRequestDTO.FastNaturalSearch request) { + return fastApiClient.sendRequest(FastApiRequestType.NATURAL_SEARCH, request); + } + +} diff --git a/src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java similarity index 79% rename from src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java rename to src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java index 440ed62..cd0c0e1 100644 --- a/src/main/java/DiffLens/back_end/global/dto/fastApi/request/FastPanelRequestDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java @@ -1,4 +1,4 @@ -package DiffLens.back_end.global.dto.fastApi.request; +package DiffLens.back_end.global.fastapi.dto.request; import lombok.AllArgsConstructor; import lombok.Builder; @@ -38,6 +38,14 @@ private static class FastSearchFilters{ } // 재검색 요청 + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastRefineSearch{ + + + } } diff --git a/src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java similarity index 91% rename from src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java rename to src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java index 2209442..feb640d 100644 --- a/src/main/java/DiffLens/back_end/global/dto/fastApi/response/FastNaturalSearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java @@ -1,5 +1,6 @@ -package DiffLens.back_end.global.dto.fastApi.response; +package DiffLens.back_end.global.fastapi.dto.response; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; From 0c86ab77b1bdd5dc782c967e630d2cb09d3b366b Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Oct 2025 18:46:38 +0900 Subject: [PATCH 27/32] =?UTF-8?q?fix=20:=20fast=20api=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EA=B2=8C=20=EC=A0=95=EC=83=81=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=91=EB=8B=B5=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20=20=20-=20Pa?= =?UTF-8?q?nel=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20:=20Pa?= =?UTF-8?q?nel=20->=20DTO=EB=A1=9C=20=EB=B3=80=EA=B2=BD.=20=20=20-=20fast?= =?UTF-8?q?=20api=EB=A1=9C=EB=B6=80=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EB=B0=98=ED=99=98=EB=90=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back_end/domain/panel/entity/Panel.java | 1 + .../panel/repository/PanelRepository.java | 41 +++++++++- .../projection/PanelWithRawDataDTO.java | 79 +++++++++++++++++++ .../back_end/domain/rawData/dto/PanelDTO.java | 34 ++++++++ .../service/RawDataUploadServiceImpl.java | 62 ++++++++++++--- .../converter/PanelResponseConverter.java | 5 +- .../search/converter/SummaryDtoConverter.java | 16 +++- .../domain/search/dto/SearchRequestDTO.java | 2 +- .../back_end/domain/search/entity/Chart.java | 1 + .../domain/search/entity/SearchHistory.java | 12 +-- .../service/implement/ChartServiceImpl.java | 46 ++++++----- .../service/implement/FilterServiceImpl.java | 14 ++-- .../implement/NaturalSearchService.java | 33 ++++++-- .../implement/SearchHistoryServiceImpl.java | 2 + .../service/interfaces/ChartService.java | 3 +- .../dto/request/FastPanelRequestDTO.java | 27 ++++--- .../FastNaturalSearchResponseDTO.java | 12 +-- .../code/status/error/SearchStatus.java | 2 + 18 files changed, 316 insertions(+), 76 deletions(-) create mode 100644 src/main/java/DiffLens/back_end/domain/panel/repository/projection/PanelWithRawDataDTO.java diff --git a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java index 97b3fc6..1dbb949 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java +++ b/src/main/java/DiffLens/back_end/domain/panel/entity/Panel.java @@ -104,6 +104,7 @@ public class Panel extends BaseEntity { @JdbcTypeCode(SqlTypes.VECTOR) @Column(columnDefinition = "vector(4096)") + @Basic(fetch = FetchType.LAZY) private float[] embedding = new float[4096]; // float[] 써야한다고 함... @JdbcTypeCode(SqlTypes.ARRAY) diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java index be77f5d..d927328 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java @@ -1,6 +1,7 @@ package DiffLens.back_end.domain.panel.repository; import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,13 +12,51 @@ public interface PanelRepository extends JpaRepository { + /** + * embedding 제외하여 조회 -> DTO 반환 + */ + @Query(""" + SELECT new DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO( + p.id, + p.gender, + p.age, + p.ageGroup, + p.birthYear, + p.residence, + p.maritalStatus, + p.childrenCount, + p.familySize, + p.education, + p.occupation, + p.job, + p.personalIncome, + p.householdIncome, + p.electronicDevices, + p.phoneBrand, + p.phoneModel, + p.carOwnership, + p.carBrand, + p.carModel, + p.smokingExperience, + p.cigaretteBrands, + p.eCigarette, + p.drinkingExperience, + p.profileSummary, + p.hashTags, + p.rawData + ) + FROM Panel p + WHERE p.id IN :ids + """) + List findPanelsWithRawDataByIds(@Param("ids") List ids); + @Query( """ SELECT p FROM Panel p WHERE p.id IN :ids """ ) - public List findByIdList(@Param("ids") List ids); + List findByIdList(@Param("ids") List ids); Page findByIdIn(List ids, Pageable pageable); diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/projection/PanelWithRawDataDTO.java b/src/main/java/DiffLens/back_end/domain/panel/repository/projection/PanelWithRawDataDTO.java new file mode 100644 index 0000000..4a0dbca --- /dev/null +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/projection/PanelWithRawDataDTO.java @@ -0,0 +1,79 @@ +package DiffLens.back_end.domain.panel.repository.projection; + +import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.rawData.entity.RawData; +import DiffLens.back_end.domain.search.enums.filters.Gender; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +/** + * 패널 전체 조회 시 Hibernate와 PostgreSQL의 vector 타입 파싱 문제로 인해 + * Panel의 embedding만 제외하고 조회하도록 하는 DTO + */ +@Getter +@AllArgsConstructor +@ToString +public class PanelWithRawDataDTO { + private String id; + private Gender gender; + private Integer age; + private String ageGroup; + private Integer birthYear; + private String residence; + private String maritalStatus; + private Integer childrenCount; + private String familySize; + private String education; + private String occupation; + private String job; + private String personalIncome; + private String householdIncome; + private List electronicDevices; + private String phoneBrand; + private String phoneModel; + private String carOwnership; + private String carBrand; + private String carModel; + private List smokingExperience; + private List cigaretteBrands; + private List eCigarette; + private List drinkingExperience; + private String profileSummary; + private List hashTags; + private RawData rawData; + + public static PanelWithRawDataDTO fromEntity(Panel panel) { + return new PanelWithRawDataDTO( + panel.getId(), + panel.getGender(), + panel.getAge(), + panel.getAgeGroup(), + panel.getBirthYear(), + panel.getResidence(), + panel.getMaritalStatus(), + panel.getChildrenCount(), + panel.getFamilySize(), + panel.getEducation(), + panel.getOccupation(), + panel.getJob(), + panel.getPersonalIncome(), + panel.getHouseholdIncome(), + panel.getElectronicDevices(), + panel.getPhoneBrand(), + panel.getPhoneModel(), + panel.getCarOwnership(), + panel.getCarBrand(), + panel.getCarModel(), + panel.getSmokingExperience(), + panel.getCigaretteBrands(), + panel.getECigarette(), + panel.getDrinkingExperience(), + panel.getProfileSummary(), + panel.getHashTags(), + panel.getRawData() + ); + } +} \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java b/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java index b850444..04b5945 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/dto/PanelDTO.java @@ -2,9 +2,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; +import java.util.Map; + import lombok.Getter; import lombok.Setter; +import static java.util.Map.entry; + @Getter @Setter public class PanelDTO { @@ -82,4 +86,34 @@ public class PanelDTO { @JsonProperty("설문응답") private SurveyResponseDTO surveyResponse; + + // 한글 컬럼명 → 실제 필드명 매핑 + public static final Map columnMapping = Map.ofEntries( + entry("panel_id", "id"), + entry("성별", "gender"), + entry("나이", "age"), + entry("연령대", "ageGroup"), + entry("출생년도", "birthYear"), + entry("거주지역", "residence"), + entry("결혼여부", "maritalStatus"), + entry("자녀수", "childrenCount"), + entry("가족수", "familySize"), + entry("최종학력", "education"), + entry("직업", "occupation"), + entry("직무", "job"), + entry("개인소득", "personalIncome"), + entry("가구소득", "householdIncome"), + entry("보유전자제품", "electronicDevices"), + entry("휴대폰브랜드", "phoneBrand"), + entry("휴대폰모델", "phoneModel"), + entry("차량보유", "carOwnership"), + entry("차량브랜드", "carBrand"), + entry("차량모델", "carModel"), + entry("흡연경험", "smokingExperience"), + entry("담배브랜드", "cigaretteBrands"), + entry("전자담배", "eCigarette"), + entry("음주경험", "drinkingExperience"), + entry("설문응답", "profileSummary") + ); + } diff --git a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java index e0c1b9e..2c60efe 100644 --- a/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/rawData/service/RawDataUploadServiceImpl.java @@ -13,10 +13,6 @@ import java.io.IOException; import java.util.*; -/** - * jpa 로 하면 메모리 터질거같아서 jdbc 씀 - * jdbcTemplate 메서드 호출하는 부분을 repository로 뺄까 했는데 복잡해질거같아서 해당 파일 안에 다 작성함. - */ @Service @RequiredArgsConstructor public class RawDataUploadServiceImpl implements RawDataUploadService { @@ -40,7 +36,7 @@ public void uploadRawData(String json) throws IOException { // 3. JSON -> PanelDTO List panelDTOList = objectMapper.readValue(json, new TypeReference>() {}); - // 4. RawData bulk insert (jsonb 캐스팅) + // 4. RawData bulk insert (jsonb) String rawDataSql = "INSERT INTO raw_data (id, json) VALUES (?, ?::jsonb)"; List rawDataBatch = new ArrayList<>(); for (PanelDTO dto : panelDTOList) { @@ -51,36 +47,76 @@ public void uploadRawData(String json) throws IOException { } jdbcTemplate.batchUpdate(rawDataSql, rawDataBatch); - // 5. Panel bulk insert (존재하지 않는 것만) - String panelSql = "INSERT INTO panel " + - "(id, gender, age_group, residence, martial_status, children_count, education, occupation, devices, profile_summary, embedding, hash_tags) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + // 5. Panel bulk insert + String panelSql = "INSERT INTO panel (" + + "id, gender, age, age_group, birth_year, residence, marital_status, children_count, family_size, education, occupation, job, " + + "personal_income, household_income, electronic_devices, phone_brand, phone_model, car_ownership, car_brand, car_model, " + + "smoking_experience, cigarette_brands, e_cigarette, drinking_experience, profile_summary, embedding, hash_tags" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; List panelBatch = new ArrayList<>(); for (PanelDTO dto : panelDTOList) { if (!existingIds.contains(dto.getPanelId())) { - String[] devicesArray = dto.getPhoneModel() != null ? new String[]{dto.getPhoneModel()} : new String[0]; + String[] devicesArray = dto.getOwnedElectronics() != null + ? dto.getOwnedElectronics().toArray(new String[0]) + : new String[0]; + + String[] smokingArray = dto.getSmokingExperience() != null + ? dto.getSmokingExperience().toArray(new String[0]) + : new String[0]; + + String[] cigBrands = dto.getCigaretteBrands() != null + ? dto.getCigaretteBrands().toArray(new String[0]) + : new String[0]; + + String[] eCig = dto.getECigarettes() != null + ? dto.getECigarettes().toArray(new String[0]) + : new String[0]; + + String[] drinking = dto.getDrinkingExperience() != null + ? dto.getDrinkingExperience().toArray(new String[0]) + : new String[0]; + + String profileJson = dto.getSurveyResponse() != null + ? objectMapper.writeValueAsString(dto.getSurveyResponse()) + : null; + + float[] embedding = new float[4096]; // 기본값 0.0f String[] hashTags = new String[0]; - // embedding은 0으로 초기화된 float 배열 - float[] embedding = new float[4096]; panelBatch.add(new Object[]{ dto.getPanelId(), Gender.fromRawDataValue(dto.getGender()).name(), + dto.getAge(), dto.getAgeGroup(), + dto.getBirthYear(), dto.getResidence(), dto.getMaritalStatus(), dto.getChildrenCount(), + dto.getFamilySize(), dto.getEducation(), dto.getOccupation(), + dto.getJob(), + dto.getPersonalIncome(), + dto.getHouseholdIncome(), devicesArray, - null, + dto.getPhoneBrand(), + dto.getPhoneModel(), + dto.getHasCar(), + dto.getCarBrand(), + dto.getCarModel(), + smokingArray, + cigBrands, + eCig, + drinking, + profileJson, embedding, hashTags }); } } + jdbcTemplate.batchUpdate(panelSql, panelBatch); } } diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java index 21b4bf0..0a7c941 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/PanelResponseConverter.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.converter; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchPanelDTO; @@ -8,9 +9,9 @@ import java.util.List; @Component -public class PanelResponseConverter implements SearchDtoConverter>{ +public class PanelResponseConverter implements SearchDtoConverter>{ @Override - public SearchPanelDTO.PanelData requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List info) { + public SearchPanelDTO.PanelData requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List info) { // TODO : 개별 응답데이터 처리 로직 작성 return new SearchPanelDTO.PanelData(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java index 1676c76..34cb5d5 100644 --- a/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java +++ b/src/main/java/DiffLens/back_end/domain/search/converter/SummaryDtoConverter.java @@ -1,30 +1,38 @@ package DiffLens.back_end.domain.search.converter; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import org.springframework.stereotype.Component; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; @Component -public class SummaryDtoConverter implements SearchDtoConverter> { +public class SummaryDtoConverter implements SearchDtoConverter> { @Override - public SearchResponseDTO.SearchResult.Summary requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List panelList) { + public SearchResponseDTO.SearchResult.Summary requestToDto(FastNaturalSearchResponseDTO.SearchResult response, List panelList) { return SearchResponseDTO.SearchResult.Summary.builder() .totalRespondents(panelList.size()) .averageAge(getAgeAvg(panelList)) - .dataCaptureDate(null) + .dataCaptureDate(getCurrentDate()) .confidenceLevel(response.getAccuracy().intValue()) .build(); } - private Double getAgeAvg(List panelList) { + private Double getAgeAvg(List panelList) { // TODO : 나이 평균 계산 return 1.0; } + private String getCurrentDate() { + LocalDate now = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + return now.format(formatter); + } } diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java index 7c189c7..20368a8 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java @@ -16,7 +16,7 @@ public class SearchRequestDTO { public static class NaturalLanguage{ @NotBlank private String question; - @NotBlank + @NotNull private QuestionMode mode; @NotNull private SearchFilters filters; diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java index 5e8fe68..cc27fc6 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/Chart.java @@ -15,6 +15,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString public class Chart extends BaseEntity { @Id diff --git a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java index 0ec1006..38418ee 100644 --- a/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java +++ b/src/main/java/DiffLens/back_end/domain/search/entity/SearchHistory.java @@ -8,6 +8,7 @@ import org.hibernate.type.SqlTypes; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; @Entity @@ -28,24 +29,25 @@ public class SearchHistory extends BaseEntity { private String content; @JdbcTypeCode(SqlTypes.ARRAY) - @Column(nullable = false, columnDefinition = "text[] DEFAULT '{}'::text[]") + @Column(columnDefinition = "text[] NOT NULL DEFAULT '{}'") private List panelIds; @JdbcTypeCode(SqlTypes.ARRAY) - @Column(nullable = false, columnDefinition = "float[] DEFAULT ") + @Column(columnDefinition = "float[] NOT NULL DEFAULT '{}'") private List concordanceRate; // 일치율 // 연관관계 - @OneToOne(cascade = CascadeType.REMOVE, mappedBy = "searchHistory") + @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, mappedBy = "searchHistory") private SearchFilter searchFilter; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") + @Setter private Member member; - @OneToMany(mappedBy = "searchHistory") - private List charts; + @OneToMany(mappedBy = "searchHistory", cascade = CascadeType.ALL, orphanRemoval = true) + private List charts = new ArrayList<>(); public void setFilter(SearchFilter searchFilter) { this.searchFilter = searchFilter; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java index 1427b7c..823124d 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/ChartServiceImpl.java @@ -1,36 +1,43 @@ package DiffLens.back_end.domain.search.service.implement; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; +import DiffLens.back_end.domain.rawData.dto.PanelDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; -import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.panel.util.ReflectionUtil; import DiffLens.back_end.domain.search.entity.Chart; import DiffLens.back_end.domain.search.entity.SearchHistory; import DiffLens.back_end.domain.search.enums.chart.ChartType; import DiffLens.back_end.domain.search.service.interfaces.ChartService; +import DiffLens.back_end.global.responses.code.status.error.SearchStatus; +import DiffLens.back_end.global.responses.exception.handler.ErrorHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.*; +import static java.util.Map.entry; + @Service @RequiredArgsConstructor public class ChartServiceImpl implements ChartService { @Override - public List makeChart(FastNaturalSearchResponseDTO.SearchResult searchResult, SearchHistory searchHistory, List foundPanels) { + public List makeChart(FastNaturalSearchResponseDTO.SearchResult searchResult, SearchHistory searchHistory, List foundPanels) { - List fastApiChartResponse = searchResult.getCharts(); + List fastApiChartResponse = searchResult.getCharts(); // fast api 로부터 받은 응답값을 List로 변환 List charts = fastApiChartResponse.stream() .map(chart -> Chart.builder() .reason(chart.getReason()) - .chartType(ChartType.valueOf(chart.getChartType())) + .chartType(ChartType.valueOf(chart.getChartType().toUpperCase())) .panelColumn(chart.getPanelColumn()) .title(chart.getTitle()) - .xAxis(chart.getXAxis()) - .yAxis(chart.getYAxis()) + .xAxis(chart.getXaxis()) + .yAxis(chart.getYaxis()) + .labels(new ArrayList<>()) + .values(new ArrayList<>()) .build() ).toList(); @@ -60,37 +67,40 @@ public List makeChart(FastNaturalSearchResponseDTO.SearchResult searchRes * @param panelColumn 패널의 어떤 데이터를 조회할 것인지 * @return Key : Label, Value : 수치 를 담은 Map */ - private Map makeDataSet(List foundPanels, String panelColumn) { - + private Map makeDataSet(List foundPanels, String panelColumn) { Map data = new LinkedHashMap<>(); - foundPanels.forEach(panel -> { - try { + String fieldName = PanelDTO.columnMapping.getOrDefault(panelColumn, null); + if(fieldName == null) { + throw new ErrorHandler(SearchStatus.INVALID_COLUMN); + } + +// if (fieldName == null) { +// return data; // 매핑 안 된 컬럼이면 빈 map 반환 +// } - // 문자열 형태의 column 정보로 panel 데이터 getter 메서드 호출 - Object value = ReflectionUtil.getFieldValue(panel, panelColumn); + for (PanelWithRawDataDTO panel : foundPanels) { + try { + Object value = ReflectionUtil.getFieldValue(panel, fieldName); if (value instanceof List list) { for (Object v : list) { if (v != null) data.merge(v.toString(), 1, Integer::sum); } - } else { + } else if (value != null) { data.merge(value.toString(), 1, Integer::sum); } } catch (Exception e) { - // 존재하지 않는 필드는 rawData에서 검색 시도 + // DTO 필드 없으면 rawData Map에서 시도 if (panel.getRawData() instanceof Map rawMap) { Object val = rawMap.get(panelColumn); if (val != null) data.merge(val.toString(), 1, Integer::sum); } } - }); + } return data; } - - - } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java index 03d2b87..f375249 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/FilterServiceImpl.java @@ -5,9 +5,11 @@ import DiffLens.back_end.domain.search.entity.SearchHistory; import DiffLens.back_end.domain.search.repository.FilterRepository; import DiffLens.back_end.domain.search.repository.SearchFilterRepository; +import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; import DiffLens.back_end.domain.search.service.interfaces.FilterService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -17,15 +19,17 @@ public class FilterServiceImpl implements FilterService { private final SearchFilterRepository searchFilterRepository; private final FilterRepository filterRepository; + private final SearchHistoryRepository searchHistoryRepository; - @Override - public SearchFilter makeFilter(List filterIdList, SearchHistory searchHistory) { + @Transactional + public SearchFilter makeFilter(List filters, SearchHistory searchHistory) { + if (searchHistory.getId() == null) { + searchHistoryRepository.save(searchHistory); // 반드시 먼저 저장 + } - // SearchFilter 객체 생성 SearchFilter searchFilter = SearchFilter.builder() - .id(searchHistory.getId()) - .filters(filterIdList) .searchHistory(searchHistory) + .filters(filters) .build(); return searchFilterRepository.save(searchFilter); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index 902d709..d2fcf6e 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -1,11 +1,14 @@ package DiffLens.back_end.domain.search.service.implement; +import DiffLens.back_end.domain.members.entity.Member; +import DiffLens.back_end.domain.members.service.auth.CurrentUserService; import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; +import DiffLens.back_end.global.fastapi.FastApiService; +import DiffLens.back_end.global.fastapi.dto.request.FastPanelRequestDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; -import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.converter.SearchDtoConverter; import DiffLens.back_end.domain.search.dto.ChartDTO; -import DiffLens.back_end.domain.search.dto.SearchPanelDTO; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; import DiffLens.back_end.domain.search.entity.Chart; @@ -30,27 +33,42 @@ public class NaturalSearchService implements SearchService> summaryConverter; + private final SearchDtoConverter> summaryConverter; private final SearchDtoConverter, SearchHistory> filterConverter; private final SearchDtoConverter, List> chartConverter; - private final SearchDtoConverter> panelResponseConverter; +// private final SearchDtoConverter> panelResponseConverter; + + private final CurrentUserService currentUserService; @Override @Transactional(readOnly = false) public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage request) { + // 현재 로그인한 유저 + Member currentUser = currentUserService.getCurrentUser(); + // TODO : 추후 fast api 에서 불러오도록 수정해야 함 - FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); + FastNaturalSearchResponseDTO.SearchResult response = fastApiService.getNaturalSearch( + FastPanelRequestDTO.FastNaturalSearch.builder() + .question(request.getQuestion()) + .mode(request.getMode().getKr()) + .filters(new FastPanelRequestDTO.FastNaturalSearch.FastSearchFilters()) + .build() + ); +// FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); // id List 추출 List panelIdList = response.getPanelList(); // DB 에서 Panel 목록 가져옴 - List foundPanels = panelRepository.findByIdList(panelIdList); + List foundPanels = panelRepository.findPanelsWithRawDataByIds(panelIdList); + // SearchHistory 데이터 생성 및 저장 SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, response); + searchHistory.setMember(currentUser); // SearchResult.Summary 생성 SearchResponseDTO.SearchResult.Summary summary = summaryConverter.requestToDto(response, foundPanels); @@ -60,13 +78,12 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re // 차트 생성 List charts = chartService.makeChart(response, searchHistory, foundPanels); charts.forEach(searchHistory::addChart); // 연관관계 편의 메서드 호출 - // TODO : 아직 차트가 저장되지는 않을거임 // 차트 변환 List graphs = chartConverter.requestToDto(null, charts); // 개별 응답 데이터 처리 및 반환 - SearchPanelDTO.PanelData panelData = panelResponseConverter.requestToDto(response, foundPanels); +// SearchPanelDTO.PanelData panelData = panelResponseConverter.requestToDto(response, foundPanels); return SearchResponseDTO.SearchResult.builder() .searchId(searchHistory.getId()) diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index c2e5d9c..2e73c44 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; @Service @@ -45,6 +46,7 @@ public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, .content(request.getQuestion()) .panelIds(fastApiResponse.getPanelList()) .concordanceRate(fastApiResponse.getAccuracyList()) + .charts(new ArrayList<>()) .build() ); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java index 146a7dd..c2bc6d7 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/ChartService.java @@ -1,5 +1,6 @@ package DiffLens.back_end.domain.search.service.interfaces; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.search.entity.Chart; @@ -8,5 +9,5 @@ import java.util.List; public interface ChartService { - public List makeChart(FastNaturalSearchResponseDTO.SearchResult fastPanelResponseDTO, SearchHistory searchHistory, List foundPanels); + public List makeChart(FastNaturalSearchResponseDTO.SearchResult fastPanelResponseDTO, SearchHistory searchHistory, List foundPanels); } diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java index cd0c0e1..b2a9dbb 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java @@ -1,9 +1,6 @@ package DiffLens.back_end.global.fastapi.dto.request; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.List; @@ -27,7 +24,11 @@ public static class FastNaturalSearch{ private FastSearchFilters filters; - private static class FastSearchFilters{ + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class FastSearchFilters{ private Integer count; private String gender; @@ -38,14 +39,14 @@ private static class FastSearchFilters{ } // 재검색 요청 - @Getter - @Builder - @AllArgsConstructor - @NoArgsConstructor - public static class FastRefineSearch{ - - - } +// @Getter +// @Builder +// @AllArgsConstructor +// @NoArgsConstructor +// public static class FastRefineSearch{ +// +// +// } } diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java index feb640d..82e79fe 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/FastNaturalSearchResponseDTO.java @@ -1,8 +1,8 @@ package DiffLens.back_end.global.fastapi.dto.response; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import java.util.List; @@ -20,6 +20,7 @@ public class FastNaturalSearchResponseDTO { */ @Getter @Setter + @ToString public static class SearchResult { // 일반적인 데이터 @@ -29,7 +30,7 @@ public static class SearchResult { private List accuracyList; // 일치율 // 차트 관련 - private List charts; + private List charts; // 개별응답 종류 // private List panelColumns; // SearchResponseDTO.EachResponses로 분리 @@ -41,12 +42,13 @@ public static class SearchResult { */ @Getter @Setter - public static class CharFastResult { + @ToString + public static class ChartFastResult { private String chartType; // 차트 유형 private String title; // 차트 제목 private String reason; // 차트 선정 이유 - private String xAxis; // x축 항목 - private String yAxis; // y축 항목 + private String xaxis; // x축 항목 + private String yaxis; // y축 항목 private String panelColumn; // 표에 표시할 정보 ex) 직업분포, 월평균 소득 등 } diff --git a/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java index 9b021bc..088401e 100644 --- a/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java +++ b/src/main/java/DiffLens/back_end/global/responses/code/status/error/SearchStatus.java @@ -13,6 +13,8 @@ public enum SearchStatus implements BaseErrorCode { SEARCH_HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH404", "검색기록을 찾을 수 없습니다."), PANEL_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH405", "패널을 찾을 수 없습니다."), + + INVALID_COLUMN(HttpStatus.INTERNAL_SERVER_ERROR, "SEARCH501", "Panel의 column을 찾을 수 없습니다."), ; private final HttpStatus httpStatus; From 8b6e122ea97ddd3348c39f887959c9f1daf35d36 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Oct 2025 18:57:56 +0900 Subject: [PATCH 28/32] =?UTF-8?q?feat=20:=20fast=20api=EC=97=90=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B3=B4=EB=82=BC=20dto=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(=ED=95=84=EB=93=9C=20=EB=AF=B8=EC=9E=91=EC=84=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/FastPanelRequestDTO.java | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java index b2a9dbb..77df200 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java @@ -39,14 +39,48 @@ public static class FastSearchFilters{ } // 재검색 요청 -// @Getter -// @Builder -// @AllArgsConstructor -// @NoArgsConstructor -// public static class FastRefineSearch{ -// -// -// } + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastRefineSearch{ + + private String tempColumn; + + } + + // 차트 + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastChart{ + + private String tempColumn; + + } + + // 추천 + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastRecommendation { + + private String tempColumn; + + } + + // 라이브러리 비교 + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FastLibraryCompare { + + private String tempColumn; + + } } From b075bc58ef4dca54a50c6dc08cebad67d1c2ed5b Mon Sep 17 00:00:00 2001 From: hardwoong Date: Fri, 24 Oct 2025 20:31:37 +0900 Subject: [PATCH 29/32] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/controller/LibraryController.java | 237 ++++++++++-------- .../library/dto/LibraryResponseDTO.java | 146 +++++++++++ .../library/service/LibraryService.java | 128 ++++++++++ 3 files changed, 404 insertions(+), 107 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java index 9b05709..3661da7 100644 --- a/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java +++ b/src/main/java/DiffLens/back_end/domain/library/controller/LibraryController.java @@ -18,111 +18,134 @@ @RequiredArgsConstructor public class LibraryController { - private final LibraryService libraryService; - private final CurrentUserService currentUserService; - - @GetMapping - @Operation(summary = "라이브러리 목록 조회", description = """ - - ## 개요 - 인증된 사용자가 생성한 라이브러리 목록을 조회하는 API입니다. - - ## 응답 - - libraries : 라이브러리 목록 배열 - - library_id : 라이브러리 ID - - library_name : 라이브러리 이름 - - tags : 라이브러리 분류 태그 리스트 - - panel_count : 저장된 패널 개수 - - created_at : 생성 시간 - - page_info : 페이징 정보 (현재는 null, 추후 구현 예정) - - ## 권한 - 본인이 생성한 라이브러리만 조회할 수 있습니다. - - ## 정렬 - 최근 생성된 순서로 정렬됩니다. - - """) - public ApiResponse libraryList() { - Member member = currentUserService.getCurrentUser(); - LibraryResponseDTO.ListResult result = libraryService.getLibrariesByMember(member); - return ApiResponse.onSuccess(result); - } - - @PostMapping - @Operation(summary = "라이브러리 생성", description = """ - - ## 개요 - 검색 기록을 기반으로 라이브러리를 생성하는 API입니다. - - ## Request Body - - search_history_id : 저장할 검색 기록의 ID (필수) - - library_name : 라이브러리 이름 (필수) - - tags : 라이브러리 분류 태그 리스트 (필수) - - panel_ids : 저장할 패널 ID 리스트 (선택, null이면 검색 기록의 모든 패널 저장) - - ## 응답 - - library_id : 생성된 라이브러리 ID - - library_name : 라이브러리 이름 - - search_history_id : 연결된 검색 기록 ID - - panel_count : 저장된 패널 개수 - - created_at : 생성 시간 - - ## 권한 - 본인의 검색 기록만 라이브러리로 저장할 수 있습니다. - - """) - public ApiResponse createLibrary( - @RequestBody @Valid LibraryRequestDto.Create request) { - Member member = currentUserService.getCurrentUser(); - LibraryService.LibraryCreateResult createResult = libraryService.createLibrary(request, member); - - LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( - createResult.getLibrary(), - request.getSearchHistoryId(), - createResult.getPanelCount()); - return ApiResponse.onSuccess(result); - } - - @PutMapping("/{libraryId}/search-histories/{searchHistoryId}") - @Operation(summary = "기존 라이브러리에 새로운 검색기록 추가(병합)", description = """ - - ## 개요 - 기존 라이브러리에 검색기록을 추가하는 API입니다. - 라이브러리의 패널 ID와 검색기록의 패널 ID를 병합하여 중복을 제거합니다. - - ## Path Parameters - - libraryId : 라이브러리 ID - - searchHistoryId : 추가할 검색기록 ID - - ## 응답 - - library_id : 라이브러리 ID - - library_name : 라이브러리 이름 - - search_history_id : 추가된 검색기록 ID - - panel_count : 새로 추가된 패널 개수 - - panel_ids : 병합된 전체 패널 ID 리스트 - - created_at : 생성 시간 - - ## 권한 - 본인의 라이브러리와 검색기록만 사용할 수 있습니다. - - ## 예시 - - 라이브러리 1번에 패널 ID [1, 2, 3]이 있음 - - 검색기록 2번에 패널 ID [3, 4, 5]가 있음 - - 결과: 라이브러리 1번에 패널 ID [1, 2, 3, 4, 5]가 됨 - - """) - public ApiResponse addSearchHistoryToLibrary( - @PathVariable Long libraryId, - @PathVariable Long searchHistoryId) { - Member member = currentUserService.getCurrentUser(); - LibraryService.LibraryCreateResult createResult = libraryService.addSearchHistoryToLibrary(libraryId, - searchHistoryId, member); - - LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( - createResult.getLibrary(), - searchHistoryId, - createResult.getPanelCount()); - return ApiResponse.onSuccess(result); - } + private final LibraryService libraryService; + private final CurrentUserService currentUserService; + + @GetMapping + @Operation(summary = "라이브러리 목록 조회", description = """ + + ## 개요 + 인증된 사용자가 생성한 라이브러리 목록을 조회하는 API입니다. + + ## 응답 + - libraries : 라이브러리 목록 배열 + - library_id : 라이브러리 ID + - library_name : 라이브러리 이름 + - tags : 라이브러리 분류 태그 리스트 + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 + - page_info : 페이징 정보 (현재는 null, 추후 구현 예정) + + ## 권한 + 본인이 생성한 라이브러리만 조회할 수 있습니다. + + ## 정렬 + 최근 생성된 순서로 정렬됩니다. + + """) + public ApiResponse libraryList() { + Member member = currentUserService.getCurrentUser(); + LibraryResponseDTO.ListResult result = libraryService.getLibrariesByMember(member); + return ApiResponse.onSuccess(result); + } + + @PostMapping + @Operation(summary = "라이브러리 생성", description = """ + + ## 개요 + 검색 기록을 기반으로 라이브러리를 생성하는 API입니다. + + ## Request Body + - search_history_id : 저장할 검색 기록의 ID (필수) + - library_name : 라이브러리 이름 (필수) + - tags : 라이브러리 분류 태그 리스트 (필수) + - panel_ids : 저장할 패널 ID 리스트 (선택, null이면 검색 기록의 모든 패널 저장) + + ## 응답 + - library_id : 생성된 라이브러리 ID + - library_name : 라이브러리 이름 + - search_history_id : 연결된 검색 기록 ID + - panel_count : 저장된 패널 개수 + - created_at : 생성 시간 + + ## 권한 + 본인의 검색 기록만 라이브러리로 저장할 수 있습니다. + + """) + public ApiResponse createLibrary( + @RequestBody @Valid LibraryRequestDto.Create request) { + Member member = currentUserService.getCurrentUser(); + LibraryService.LibraryCreateResult createResult = libraryService.createLibrary(request, member); + + LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( + createResult.getLibrary(), + request.getSearchHistoryId(), + createResult.getPanelCount()); + return ApiResponse.onSuccess(result); + } + + @GetMapping("/{libraryId}") + @Operation(summary = "특정 라이브러리 상세 조회", description = """ + ## 개요 + 특정 라이브러리의 상세 정보를 조회하는 API입니다. + + ## Path Parameters + - libraryId : 조회할 라이브러리 ID + + ## 응답 데이터 + - 라이브러리 기본 정보 (이름, 태그, 생성일, 수정일) + - 포함된 패널들의 상세 정보 + - 연결된 검색기록들 -> 혹시 몰라 추가한 로직으로 필요 없다고 판단될 시 안쓰셔도 됩니다. + - 패널 통계 정보 (성별, 연령대, 거주지 분포) -> 혹시 몰라 추가한 로직으로 필요 없다고 판단될 시 안쓰셔도 됩니다. + + ## 권한 + 본인이 생성한 라이브러리만 조회할 수 있습니다. + """) + public ApiResponse getLibraryDetail(@PathVariable Long libraryId) { + Member member = currentUserService.getCurrentUser(); + LibraryResponseDTO.LibraryDetail result = libraryService.getLibraryDetail(libraryId, member); + return ApiResponse.onSuccess(result); + } + + @PutMapping("/{libraryId}/search-histories/{searchHistoryId}") + @Operation(summary = "기존 라이브러리에 새로운 검색기록 추가(병합)", description = """ + + ## 개요 + 기존 라이브러리에 검색기록을 추가하는 API입니다. + 라이브러리의 패널 ID와 검색기록의 패널 ID를 병합하여 중복을 제거합니다. + + ## Path Parameters + - libraryId : 라이브러리 ID + - searchHistoryId : 추가할 검색기록 ID + + ## 응답 + - library_id : 라이브러리 ID + - library_name : 라이브러리 이름 + - search_history_id : 추가된 검색기록 ID + - panel_count : 새로 추가된 패널 개수 + - panel_ids : 병합된 전체 패널 ID 리스트 + - created_at : 생성 시간 + + ## 권한 + 본인의 라이브러리와 검색기록만 사용할 수 있습니다. + + ## 예시 + - 라이브러리 1번에 패널 ID [1, 2, 3]이 있음 + - 검색기록 2번에 패널 ID [3, 4, 5]가 있음 + - 결과: 라이브러리 1번에 패널 ID [1, 2, 3, 4, 5]가 됨 + + """) + public ApiResponse addSearchHistoryToLibrary( + @PathVariable Long libraryId, + @PathVariable Long searchHistoryId) { + Member member = currentUserService.getCurrentUser(); + LibraryService.LibraryCreateResult createResult = libraryService.addSearchHistoryToLibrary(libraryId, + searchHistoryId, member); + + LibraryResponseDTO.CreateResult result = LibraryResponseDTO.CreateResult.from( + createResult.getLibrary(), + searchHistoryId, + createResult.getPanelCount()); + return ApiResponse.onSuccess(result); + } } diff --git a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java index 7b7909b..534f809 100644 --- a/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/library/dto/LibraryResponseDTO.java @@ -99,4 +99,150 @@ public static CreateResult from(Library library, Long searchHistoryId, int panel } } + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class LibraryDetail { + + @JsonProperty("library_id") + private Long libraryId; + + @JsonProperty("library_name") + private String libraryName; + + private List tags; + + @JsonProperty("panel_count") + private Integer panelCount; + + @JsonProperty("panel_ids") + private List panelIds; + + @JsonProperty("panels") + private List panels; + + @JsonProperty("search_histories") + private List searchHistories; + + @JsonProperty("statistics") + private Statistics statistics; + + @JsonProperty("created_at") + private String createdAt; + + @JsonProperty("updated_at") + private String updatedAt; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class PanelInfo { + @JsonProperty("panel_id") + private String panelId; + + @JsonProperty("gender") + private String gender; + + @JsonProperty("age") + private Integer age; + + @JsonProperty("age_group") + private String ageGroup; + + @JsonProperty("residence") + private String residence; + + @JsonProperty("marital_status") + private String maritalStatus; + + @JsonProperty("children_count") + private Integer childrenCount; + + @JsonProperty("occupation") + private String occupation; + + @JsonProperty("profile_summary") + private String profileSummary; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SearchHistoryInfo { + @JsonProperty("search_history_id") + private Long searchHistoryId; + + @JsonProperty("content") + private String content; + + @JsonProperty("date") + private String date; + + @JsonProperty("panel_count") + private Integer panelCount; + + @JsonProperty("created_at") + private String createdAt; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Statistics { + @JsonProperty("total_panels") + private Integer totalPanels; + + @JsonProperty("gender_distribution") + private GenderDistribution genderDistribution; + + @JsonProperty("age_group_distribution") + private AgeGroupDistribution ageGroupDistribution; + + @JsonProperty("residence_distribution") + private ResidenceDistribution residenceDistribution; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GenderDistribution { + private Integer male; + private Integer female; + private Integer none; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class AgeGroupDistribution { + @JsonProperty("20대") + private Integer twenties; + @JsonProperty("30대") + private Integer thirties; + @JsonProperty("40대") + private Integer forties; + @JsonProperty("50대") + private Integer fifties; + @JsonProperty("60대+") + private Integer sixtiesPlus; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ResidenceDistribution { + private Integer seoul; + private Integer gyeonggi; + private Integer busan; + private Integer other; + } + } + } + } diff --git a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java index 8c30ab8..3879087 100644 --- a/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java +++ b/src/main/java/DiffLens/back_end/domain/library/service/LibraryService.java @@ -156,6 +156,134 @@ public LibraryResponseDTO.ListResult getLibrariesByMember(Member member) { .build(); } + @Transactional(readOnly = true) + public LibraryResponseDTO.LibraryDetail getLibraryDetail(Long libraryId, Member member) { + // 1. 라이브러리 조회 및 권한 검증 + Library library = libraryRepository.findById(libraryId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.BAD_REQUEST)); + + if (!library.getMember().getId().equals(member.getId())) { + throw new ErrorHandler(ErrorStatus.FORBIDDEN); + } + + // 2. 패널 정보 조회 + List panels = panelRepository.findByIdList(library.getPanelIds()); + List panelInfos = panels.stream() + .map(panel -> LibraryResponseDTO.LibraryDetail.PanelInfo.builder() + .panelId(panel.getId()) + .gender(panel.getGender() != null ? panel.getGender().toString() : null) + .age(panel.getAge()) + .ageGroup(panel.getAgeGroup()) + .residence(panel.getResidence()) + .maritalStatus(panel.getMaritalStatus()) + .childrenCount(panel.getChildrenCount()) + .occupation(panel.getOccupation()) + .profileSummary(panel.getProfileSummary()) + .build()) + .toList(); + + // 3. 연결된 검색기록 조회 + List searchHistoryLibraries = searchHistoryLibraryRepository + .findByLibraryId(libraryId); + List searchHistoryInfos = searchHistoryLibraries + .stream() + .map(shl -> { + SearchHistory history = shl.getHistory(); + return LibraryResponseDTO.LibraryDetail.SearchHistoryInfo.builder() + .searchHistoryId(history.getId()) + .content(history.getContent()) + .date(history.getDate() != null ? history.getDate().toString() + : null) + .panelCount(history.getPanelIds() != null + ? history.getPanelIds().size() + : 0) + .createdAt(history.getCreatedDate() != null + ? history.getCreatedDate().toString() + : null) + .build(); + }) + .toList(); + + // 4. 통계 정보 생성 + LibraryResponseDTO.LibraryDetail.Statistics statistics = createStatistics(panels); + + return LibraryResponseDTO.LibraryDetail.builder() + .libraryId(library.getId()) + .libraryName(library.getLibraryName()) + .tags(library.getTags()) + .panelCount(panels.size()) + .panelIds(library.getPanelIds()) + .panels(panelInfos) + .searchHistories(searchHistoryInfos) + .statistics(statistics) + .createdAt(library.getCreatedDate() != null ? library.getCreatedDate().toString() + : null) + .updatedAt(library.getUpdatedAt() != null ? library.getUpdatedAt().toString() : null) + .build(); + } + + // 추후 혹시 라이브러리 상세 페이지에서 간단한 통계 디자인 생길 것 대비해 작성해둠 + private LibraryResponseDTO.LibraryDetail.Statistics createStatistics(List panels) { + // 성별 분포 + long maleCount = panels.stream() + .filter(p -> p.getGender() != null && p.getGender().toString().equals("MALE")).count(); + long femaleCount = panels.stream() + .filter(p -> p.getGender() != null && p.getGender().toString().equals("FEMALE")) + .count(); + long noneCount = panels.stream() + .filter(p -> p.getGender() != null && p.getGender().toString().equals("NONE")).count(); + + LibraryResponseDTO.LibraryDetail.Statistics.GenderDistribution genderDistribution = LibraryResponseDTO.LibraryDetail.Statistics.GenderDistribution + .builder() + .male((int) maleCount) + .female((int) femaleCount) + .none((int) noneCount) + .build(); + + // 연령대 분포 + long twenties = panels.stream().filter(p -> "20대".equals(p.getAgeGroup())).count(); + long thirties = panels.stream().filter(p -> "30대".equals(p.getAgeGroup())).count(); + long forties = panels.stream().filter(p -> "40대".equals(p.getAgeGroup())).count(); + long fifties = panels.stream().filter(p -> "50대".equals(p.getAgeGroup())).count(); + long sixtiesPlus = panels.stream().filter(p -> p.getAgeGroup() != null && + (p.getAgeGroup().contains("60") || p.getAgeGroup().contains("70") + || p.getAgeGroup().contains("80"))) + .count(); + + LibraryResponseDTO.LibraryDetail.Statistics.AgeGroupDistribution ageGroupDistribution = LibraryResponseDTO.LibraryDetail.Statistics.AgeGroupDistribution + .builder() + .twenties((int) twenties) + .thirties((int) thirties) + .forties((int) forties) + .fifties((int) fifties) + .sixtiesPlus((int) sixtiesPlus) + .build(); + + // 거주지 분포 + long seoul = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("서울")) + .count(); + long gyeonggi = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("경기")) + .count(); + long busan = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("부산")) + .count(); + long other = panels.size() - seoul - gyeonggi - busan; + + LibraryResponseDTO.LibraryDetail.Statistics.ResidenceDistribution residenceDistribution = LibraryResponseDTO.LibraryDetail.Statistics.ResidenceDistribution + .builder() + .seoul((int) seoul) + .gyeonggi((int) gyeonggi) + .busan((int) busan) + .other((int) other) + .build(); + + return LibraryResponseDTO.LibraryDetail.Statistics.builder() + .totalPanels(panels.size()) + .genderDistribution(genderDistribution) + .ageGroupDistribution(ageGroupDistribution) + .residenceDistribution(residenceDistribution) + .build(); + } + private void createLibraryPanels(Library library, List panelIds) { List panels = panelRepository.findByIdList(panelIds); From e0629e10c1e1a965d286edb5d2e61daabe870e29 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Oct 2025 00:59:35 +0900 Subject: [PATCH 30/32] =?UTF-8?q?refactor=20:=20=EA=B2=80=EC=83=89=20api?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=20=20-=20=EA=B0=9C=EB=B3=84=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20api=20?= =?UTF-8?q?=EC=A0=95=EC=83=81=20=EC=A1=B0=ED=9A=8C=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20=20=20-=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/repository/PanelRepository.java | 35 +++++++++++++++++++ .../search/controller/SearchController.java | 11 +++--- .../domain/search/dto/SearchResponseDTO.java | 9 +++-- .../implement/NaturalSearchService.java | 2 +- .../implement/SearchHistoryServiceImpl.java | 4 +-- .../implement/SearchPanelServiceImpl.java | 6 ++-- .../interfaces/SearchPanelService.java | 4 +-- .../dto/request/FastPanelRequestDTO.java | 34 +++++++++++------- 8 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java index d927328..26edd0f 100644 --- a/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java +++ b/src/main/java/DiffLens/back_end/domain/panel/repository/PanelRepository.java @@ -50,6 +50,41 @@ public interface PanelRepository extends JpaRepository { """) List findPanelsWithRawDataByIds(@Param("ids") List ids); + @Query(""" + SELECT new DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO( + p.id, + p.gender, + p.age, + p.ageGroup, + p.birthYear, + p.residence, + p.maritalStatus, + p.childrenCount, + p.familySize, + p.education, + p.occupation, + p.job, + p.personalIncome, + p.householdIncome, + p.electronicDevices, + p.phoneBrand, + p.phoneModel, + p.carOwnership, + p.carBrand, + p.carModel, + p.smokingExperience, + p.cigaretteBrands, + p.eCigarette, + p.drinkingExperience, + p.profileSummary, + p.hashTags, + p.rawData + ) + FROM Panel p + WHERE p.id IN :ids + """) + Page findPanelsWithRawDataByIdsInPage(@Param("ids") List ids, Pageable pageable); + @Query( """ SELECT p FROM Panel p diff --git a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java index 4d29107..1206cf4 100644 --- a/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java +++ b/src/main/java/DiffLens/back_end/domain/search/controller/SearchController.java @@ -22,7 +22,7 @@ public class SearchController { private final SearchHistoryService searchHistoryService; @PostMapping - @Operation(summary = "자연어 검색 ( 부분개발, 테스트 전 )", + @Operation(summary = "자연어 검색 ( ai 연동 전 )", description = """ ## 개요 @@ -30,9 +30,10 @@ public class SearchController { ## request body - 검색 모드와 필터 항목들은 노션에 정리하여 올리겠습니다. + - 필터에는 Filter Code 를 넣어주세요. ex) 101, 203, 305 ... ## 응답 - 검색 결과에 개별응답은 포함하지 않았습니다. 개별 API를 조회해야 합니다. + 검색 결과에 개별응답은 포함하지 않았습니다. 개별 응답 데이터 API를 조회해야 합니다. """) public ApiResponse naturalLanguage(@RequestBody @Valid SearchRequestDTO.NaturalLanguage request) { @@ -41,14 +42,14 @@ public ApiResponse naturalLanguage(@RequestBody } @PostMapping("/refine") - @Operation(summary = "기존 검색 결과 기반 재검색 ( 미구현 )") + @Operation(summary = "기존 검색 결과 기반 재검색 ( 미구현 )", description = "아직 구현 전이지만 아마 자연어 검색과 같은 형태로 반환될 듯 싶습니다.") public ApiResponse refine(@RequestBody @Valid SearchRequestDTO.ExistingSearchResult request) { SearchResponseDTO.SearchResult result = new SearchResponseDTO.SearchResult(); // 임시 result return ApiResponse.onSuccess(result); } @GetMapping("/{searchId}/each-responses") - @Operation(summary = "개별 응답 데이터 ( 테스트 전 )", + @Operation(summary = "개별 응답 데이터 ( 완료 )", description = """ ## 개요 @@ -70,7 +71,7 @@ public ApiResponse eachResponses(@PathVariable( } @GetMapping("/recommended") - @Operation(summary = "맞춤 검색 추천 ( 미구현 )") + @Operation(summary = "맞춤 검색 추천 ( 미구현 )", description = "요청 형태 확정 후 구현하겠습니다.") public ApiResponse refine(@RequestBody @Valid SearchRequestDTO.SearchFilters request) { return ApiResponse.onSuccess(null); } diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java index b5c0536..9af1383 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java @@ -1,7 +1,6 @@ package DiffLens.back_end.domain.search.dto; -import DiffLens.back_end.domain.panel.entity.Panel; -import DiffLens.back_end.domain.rawData.dto.PanelDTO; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.dto.ResponsePageDTO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -96,13 +95,13 @@ public static class ResponseValues{ @JsonProperty("concordance_rate") private String concordanceRate; // 일치율 - public static ResponseValues fromPanelDTO(Panel panel, String concordanceRate) { + public static ResponseValues fromPanelDTO(PanelWithRawDataDTO panel, String concordanceRate) { return ResponseValues.builder() .respondentId(panel.getId()) .gender(panel.getGender().getDisplayValue()) - .age("TODO : 나이 넣기") // TODO : Panel에 나이 생기면 넣기 + .age(panel.getAge().toString()) // TODO : Panel에 나이 생기면 넣기 .residence(panel.getResidence()) - .personalIncome("TODO : 개인소득 넣기") // TODO : Panel에 개인소득 생기면 넣기 + .personalIncome(panel.getPersonalIncome()) // TODO : Panel에 개인소득 생기면 넣기 .concordanceRate(concordanceRate) .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java index d2fcf6e..a02fd02 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/NaturalSearchService.java @@ -55,7 +55,7 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re FastPanelRequestDTO.FastNaturalSearch.builder() .question(request.getQuestion()) .mode(request.getMode().getKr()) - .filters(new FastPanelRequestDTO.FastNaturalSearch.FastSearchFilters()) + .filters(new FastPanelRequestDTO.FastSearchFilters()) .build() ); // FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index 2e73c44..a4ffb26 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalSearchResponseDTO; -import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.rawData.service.RawDataService; import DiffLens.back_end.domain.search.dto.SearchRequestDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; @@ -92,7 +92,7 @@ public SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, In Pageable pageable = PageRequest.of(pageNum - 1, size); // PanelId 목록을 이용해서 Panel 조회 - Page panelDtoList = searchPanelService.getPanelDtoList(panelIds, pageable); + Page panelDtoList = searchPanelService.getPanelDtoList(panelIds, pageable); // 페이지 범위 초과 검사 추가 if (pageNum > panelDtoList.getTotalPages() && panelDtoList.getTotalPages() > 0) { diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java index 66dacea..21d22e5 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchPanelServiceImpl.java @@ -1,7 +1,7 @@ package DiffLens.back_end.domain.search.service.implement; -import DiffLens.back_end.domain.panel.entity.Panel; import DiffLens.back_end.domain.panel.repository.PanelRepository; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.domain.search.service.interfaces.SearchPanelService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -17,8 +17,8 @@ public class SearchPanelServiceImpl implements SearchPanelService { private final PanelRepository panelRepository; @Override - public Page getPanelDtoList(List panelIds, Pageable pageable) { - return panelRepository.findByIdIn(panelIds, pageable); + public Page getPanelDtoList(List panelIds, Pageable pageable) { + return panelRepository.findPanelsWithRawDataByIdsInPage(panelIds, pageable); } } diff --git a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java index 34f2145..4592322 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/interfaces/SearchPanelService.java @@ -1,6 +1,6 @@ package DiffLens.back_end.domain.search.service.interfaces; -import DiffLens.back_end.domain.panel.entity.Panel; +import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -8,6 +8,6 @@ public interface SearchPanelService { - Page getPanelDtoList(List panelIds, Pageable pageable); + Page getPanelDtoList(List panelIds, Pageable pageable); } diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java index 77df200..cfecc6c 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/FastPanelRequestDTO.java @@ -24,18 +24,6 @@ public static class FastNaturalSearch{ private FastSearchFilters filters; - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class FastSearchFilters{ - - private Integer count; - private String gender; - private List filters; - - } - } // 재검색 요청 @@ -45,7 +33,12 @@ public static class FastSearchFilters{ @NoArgsConstructor public static class FastRefineSearch{ - private String tempColumn; + private String question; + + private String mode; + + private FastSearchFilters filters; + } @@ -82,5 +75,20 @@ public static class FastLibraryCompare { } + // ------------------------------- + // 아 아래는 위에서 필요한 클래스들 + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class FastSearchFilters{ + + private Integer count; + private String gender; + private List filters; + + } + } From cce8ac6455d9b418b5399b32ce25f31ec1ceff18 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Oct 2025 01:05:00 +0900 Subject: [PATCH 31/32] =?UTF-8?q?docs=20:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B5=AC=EA=B8=80=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20hide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back_end/domain/members/controller/AuthController.java | 2 +- .../back_end/global/security/JwtTokenExpirationTime.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/members/controller/AuthController.java b/src/main/java/DiffLens/back_end/domain/members/controller/AuthController.java index a20b168..b712871 100644 --- a/src/main/java/DiffLens/back_end/domain/members/controller/AuthController.java +++ b/src/main/java/DiffLens/back_end/domain/members/controller/AuthController.java @@ -59,7 +59,7 @@ public ApiResponse localLogin(@RequestBody @Valid Auth } @PostMapping("/login/google") - @Operation(summary = "로그인 ( 구글 )", + @Operation(summary = "로그인 ( 구글 )", hidden = true, description = """ ## 개요 diff --git a/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java b/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java index ef4ba42..573d655 100644 --- a/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java +++ b/src/main/java/DiffLens/back_end/global/security/JwtTokenExpirationTime.java @@ -7,7 +7,7 @@ @AllArgsConstructor public enum JwtTokenExpirationTime { - ACCESS_TOKEN(1000L * 60 * 30), // 30분 + ACCESS_TOKEN(1000L * 60 * 60), // 60분 REFRESH_TOKEN(1000L * 60 * 60 * 24 * 14), ; From c91a518a8c66001c05f189ee86754a1eb632f62b Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Oct 2025 02:12:31 +0900 Subject: [PATCH 32/32] =?UTF-8?q?fix=20:=20=EA=B2=80=EC=83=89=20api=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20=20=20-=20=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=EC=9D=91=EB=8B=B5=EC=9D=98=20=EB=82=98=EC=9D=B4=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20npe=20=EB=B0=A9=EC=A7=80=20=20=20?= =?UTF-8?q?-=20=EA=B2=80=EC=83=89=20api=20request=20body=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=95=AD=EB=AA=A9=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back_end/domain/search/dto/SearchRequestDTO.java | 6 +++--- .../back_end/domain/search/dto/SearchResponseDTO.java | 4 ++-- .../back_end/domain/search/enums/filters/Gender.java | 2 +- .../back_end/domain/search/enums/filters/MartialStatus.java | 2 +- .../search/service/implement/SearchHistoryServiceImpl.java | 4 ++-- .../DiffLens/back_end/global/security/SecurityConfig.java | 1 + 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java index 20368a8..cdbf760 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchRequestDTO.java @@ -19,7 +19,7 @@ public static class NaturalLanguage{ @NotNull private QuestionMode mode; @NotNull - private SearchFilters filters; + private List filters; } // 기존 검색 결과 기반 재검색 @@ -38,8 +38,8 @@ public static class ExistingSearchResult{ @Getter @Setter public static class SearchFilters{ - private Integer count; - private String gender; +// private Integer count; +// private String gender; private List filters; // age_group:TWENTY // private Respondent respondent; // @JsonProperty(value = "age_group") diff --git a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java index 9af1383..d564e32 100644 --- a/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java +++ b/src/main/java/DiffLens/back_end/domain/search/dto/SearchResponseDTO.java @@ -99,9 +99,9 @@ public static ResponseValues fromPanelDTO(PanelWithRawDataDTO panel, String conc return ResponseValues.builder() .respondentId(panel.getId()) .gender(panel.getGender().getDisplayValue()) - .age(panel.getAge().toString()) // TODO : Panel에 나이 생기면 넣기 + .age(panel.getAge() != null ? panel.getAge().toString() : null) .residence(panel.getResidence()) - .personalIncome(panel.getPersonalIncome()) // TODO : Panel에 개인소득 생기면 넣기 + .personalIncome(panel.getPersonalIncome()) .concordanceRate(concordanceRate) .build(); } diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java index 0bf9dee..a5efb38 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/Gender.java @@ -11,7 +11,7 @@ public enum Gender { MALE(1L, "남성", "남성"), FEMALE(2L, "여성", "여성"), - NONE(3L, "알수없음", null) + NONE(3L, "기타", null) ; diff --git a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java index db89cb7..96616e6 100644 --- a/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java +++ b/src/main/java/DiffLens/back_end/domain/search/enums/filters/MartialStatus.java @@ -13,7 +13,7 @@ public enum MartialStatus { MARRIED(2L, "기혼", "기혼"), DIVORCE(3L, "이혼", "이혼"), DEATH(4L, "사별", "사별"), - NONE(5L, "알수없음", null) + NONE(5L, "기타", null) ; diff --git a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java index a4ffb26..b888197 100644 --- a/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java +++ b/src/main/java/DiffLens/back_end/domain/search/service/implement/SearchHistoryServiceImpl.java @@ -34,7 +34,7 @@ public class SearchHistoryServiceImpl implements SearchHistoryService { private final RawDataService rawDataService; private final FilterService filterService; - private final List keys = List.of("응답자ID", "성별", "나이", "거주지", "월소득", "일치율"); + private final List keys = List.of("응답자ID-respondent_id", "성별-gender", "나이-age", "거주지-residence", "월소득-personal_income", "일치율-concordance_rate"); @Override public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastNaturalSearchResponseDTO.SearchResult fastApiResponse) { @@ -51,7 +51,7 @@ public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, ); // SearchFilter 생성 - SearchFilter searchFilter = filterService.makeFilter(request.getFilters().getFilters(), searchHistory); + SearchFilter searchFilter = filterService.makeFilter(request.getFilters(), searchHistory); // SearchHistory의 Filter를 지정 searchHistory.setFilter(searchFilter); diff --git a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java index 24a036c..cbedc6d 100644 --- a/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java +++ b/src/main/java/DiffLens/back_end/global/security/SecurityConfig.java @@ -55,6 +55,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/v3/api-docs/**", "/auth/signup/**", "/auth/login/**", + "/auth/reissue", "/oauth2/**" // "/admin/**" ).permitAll()