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 d95c2ec..c0f2da1 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 @@ -21,6 +21,7 @@ public class Library extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "library_id") private Long id; @Column(nullable = false, length = 50) 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 b0e40ba..756f0c8 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 @@ -187,7 +187,7 @@ public LibraryResponseDTO.LibraryDetail getLibraryDetail(Long libraryId, Member .gender(panel.getGender() != null ? panel.getGender().toString() : null) .age(panel.getAge()) .ageGroup(panel.getAgeGroup()) - .residence(panel.getResidence()) + .residence(panel.getRegion()) .maritalStatus(panel.getMaritalStatus()) .childrenCount(panel.getChildrenCount()) .occupation(panel.getOccupation()) @@ -273,11 +273,11 @@ private LibraryResponseDTO.LibraryDetail.Statistics createStatistics(List .build(); // 거주지 분포 - long seoul = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("서울")) + long seoul = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("서울")) .count(); - long gyeonggi = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("경기")) + long gyeonggi = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("경기")) .count(); - long busan = panels.stream().filter(p -> p.getResidence() != null && p.getResidence().contains("부산")) + long busan = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("부산")) .count(); long other = panels.size() - seoul - gyeonggi - busan; @@ -378,9 +378,9 @@ private LibraryCompareResponseDTO.GroupMetrics getGroupMetrics(Library library) long maleCount = panels.stream().filter(p -> p.getGender() != null && p.getGender().toString().equals(Gender.MALE)).count(); long femaleCount = panels.stream().filter(p -> p.getGender() != null && p.getGender().toString().equals(Gender.FEMALE)).count(); - 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 seoul = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("서울")).count(); + long gyeonggi = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("경기")).count(); + long busan = panels.stream().filter(p -> p.getRegion() != null && p.getRegion().contains("부산")).count(); long regionEtc = total - seoul - gyeonggi - busan; long carOwners = panels.stream().filter(p -> p.getCarOwnership() != null && p.getCarOwnership().contains("있음")).count(); 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 f261253..6765217 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 @@ -13,6 +13,7 @@ public class Agreement extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "agreement_id") private Long id; @Column(nullable = false, columnDefinition = "TEXT") 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 3572da1..fe50538 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 @@ -22,6 +22,7 @@ public class Member extends BaseEntity implements UserDetails { // 식별자 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") private Long id; // 일반 속성 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 0a4aa4c..f423318 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 @@ -15,6 +15,7 @@ public class Onboarding extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "onboarding_id") private Long id; @Enumerated(EnumType.STRING) 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 f8ac405..eafe847 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,6 +13,7 @@ public class Plan extends BaseEntity { @Id + @Column(name = "plan_id") private Long id; @Column(nullable = false, length = 50) 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 18423f2..880a316 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 @@ -20,7 +20,7 @@ public class Panel extends BaseEntity { @Id - @Column(length = 50) + @Column(name = "panel_id", length = 50) private String id; @Enumerated(EnumType.STRING) @@ -36,6 +36,9 @@ public class Panel extends BaseEntity { @Column(length = 4) private Integer birthYear; + @Column(length = 100) + private String region; + @Column(length = 100) private String residence; @@ -109,8 +112,8 @@ public class Panel extends BaseEntity { private float[] embedding = new float[4096]; // float[] 써야한다고 함... @JdbcTypeCode(SqlTypes.ARRAY) - @Column(columnDefinition = "text[]") - private List hashTags = new ArrayList<>(); + @Column(name = "hashtags", columnDefinition = "text[]") + private List hashtags = new ArrayList<>(); // 연관관계 @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) 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 26edd0f..bd8e75e 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 @@ -22,6 +22,7 @@ public interface PanelRepository extends JpaRepository { p.age, p.ageGroup, p.birthYear, + p.region, p.residence, p.maritalStatus, p.childrenCount, @@ -42,7 +43,7 @@ public interface PanelRepository extends JpaRepository { p.eCigarette, p.drinkingExperience, p.profileSummary, - p.hashTags, + p.hashtags, p.rawData ) FROM Panel p @@ -57,6 +58,7 @@ public interface PanelRepository extends JpaRepository { p.age, p.ageGroup, p.birthYear, + p.region, p.residence, p.maritalStatus, p.childrenCount, @@ -77,7 +79,7 @@ public interface PanelRepository extends JpaRepository { p.eCigarette, p.drinkingExperience, p.profileSummary, - p.hashTags, + p.hashtags, p.rawData ) FROM Panel p 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 index 4a0dbca..9705e15 100644 --- 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 @@ -22,6 +22,7 @@ public class PanelWithRawDataDTO { private Integer age; private String ageGroup; private Integer birthYear; + private String region; private String residence; private String maritalStatus; private Integer childrenCount; @@ -52,6 +53,7 @@ public static PanelWithRawDataDTO fromEntity(Panel panel) { panel.getAge(), panel.getAgeGroup(), panel.getBirthYear(), + panel.getRegion(), panel.getResidence(), panel.getMaritalStatus(), panel.getChildrenCount(), @@ -72,7 +74,7 @@ public static PanelWithRawDataDTO fromEntity(Panel panel) { panel.getECigarette(), panel.getDrinkingExperience(), panel.getProfileSummary(), - panel.getHashTags(), + panel.getHashtags(), panel.getRawData() ); } 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 382a4d7..2cc91fb 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 @@ -34,7 +34,7 @@ public PanelResponseDTO.PanelDetails getPanelDetails(String panelId) { .summary(panel.getProfileSummary()) .basicInfo(PanelResponseDTO.PanelDetails.PanelDetail.BasicInfo.builder() .gender(panel.getGender() != null ? panel.getGender().toString() : null) - .residence(panel.getResidence()) + .residence(panel.getRegion()) .maritalStatus(panel.getMaritalStatus()) .childrenCount(panel.getChildrenCount()) .occupation(panel.getOccupation()) @@ -65,7 +65,7 @@ private List createAttribut .build(), PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() .key("residence") - .value(panel.getResidence()) + .value(panel.getRegion()) .build(), PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() .key("marital_status") @@ -143,7 +143,7 @@ private List createAttribut .build(), PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() .key("hash_tags") - .value(panel.getHashTags().toString()) + .value(panel.getHashtags().toString()) .build(), PanelResponseDTO.PanelDetails.PanelDetail.Attribute.builder() .key("raw_data") 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 f24fd57..c327f2f 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 @@ -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(FastNaturalLanguageResponseDTO.NaturalSearch response, SearchHistory searchHistory) { + public List requestToDto(Void v, SearchHistory searchHistory) { List filterIdList = searchHistory.getSearchFilter().getFilters(); List filters = filterService.findFilters(filterIdList); 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 fcfc82c..acf3201 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 @@ -4,6 +4,7 @@ import DiffLens.back_end.domain.panel.repository.projection.PanelWithRawDataDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalLanguageResponseDTO; import DiffLens.back_end.domain.search.dto.SearchResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.MainSearchResponse; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -11,22 +12,28 @@ import java.util.List; @Component -public class SummaryDtoConverter implements SearchDtoConverter> { +public class SummaryDtoConverter implements SearchDtoConverter> { @Override - public SearchResponseDTO.SearchResult.Summary requestToDto(FastNaturalLanguageResponseDTO.Data response, List panelList) { + public SearchResponseDTO.SearchResult.Summary requestToDto(MainSearchResponse response, List panelList) { return SearchResponseDTO.SearchResult.Summary.builder() .totalRespondents(panelList.size()) - .averageAge(getAgeAvg(panelList)) + .averageAge(getAgeAvg(response)) .dataCaptureDate(getCurrentDate()) - .confidenceLevel(response.getAccuracy() != null ? response.getAccuracy().intValue() : null) + .confidenceLevel(null) +// .confidenceLevel(response.getAccuracy() != null ? response.getAccuracy().intValue() : null) .build(); } - private Double getAgeAvg(List panelList) { - // TODO : 나이 평균 계산 - return 1.0; + private Double getAgeAvg(MainSearchResponse response) { + + List panels = response.getPanels(); + return panels.stream() + .filter(p -> p.getAge() != null) // null 제외 + .mapToInt(MainSearchResponse.PanelInfo::getAge) + .average() + .orElse(0.0); } private String getCurrentDate() { 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 cc27fc6..30fe67e 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 @@ -20,6 +20,7 @@ public class Chart extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "chart_id") private Long id; @Column(nullable = false, length = 100) 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 a7dcb56..ae1f343 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 @@ -12,6 +12,7 @@ public class Filter extends BaseEntity { @Id + @Column(name = "filter_id") private Long id; // 자동생성 X @Column(nullable = false, length = 50) 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 38418ee..7a34f77 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 @@ -64,4 +64,8 @@ public void removeChart(Chart chart) { chart.setSearchHistory(null); } + public void setCreatedAt(LocalDate date) { + this.date = date; + } + } 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 e8527c6..6511660 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 @@ -18,4 +18,12 @@ public interface FilterRepository extends JpaRepository { ) List findByIds(@Param("ids") Set ids); + @Query( + """ + SELECT f from Filter f + WHERE f.id in :ids + """ + ) + List findByIds(@Param("ids") List ids); + } 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 f3c0c19..2fa44cb 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 @@ -4,9 +4,13 @@ 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.domain.search.entity.Filter; import DiffLens.back_end.domain.search.enums.chart.ChartType; +import DiffLens.back_end.domain.search.repository.FilterRepository; +import DiffLens.back_end.domain.search.repository.SearchHistoryRepository; import DiffLens.back_end.global.fastapi.FastApiService; import DiffLens.back_end.global.fastapi.dto.request.FastNaturalLanguageRequestDTO; +import DiffLens.back_end.global.fastapi.dto.request.MainSearchRequest; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalLanguageResponseDTO; import DiffLens.back_end.domain.search.converter.SearchDtoConverter; import DiffLens.back_end.domain.search.dto.ChartDTO; @@ -17,11 +21,16 @@ 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 DiffLens.back_end.global.fastapi.dto.response.MainSearchResponse; +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 org.springframework.transaction.annotation.Transactional; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * 자연어 Service --> SearchService 구현 @@ -32,17 +41,19 @@ public class NaturalSearchService implements SearchService> summaryConverter; - private final SearchDtoConverter, SearchHistory> filterConverter; + private final SearchDtoConverter> summaryConverter; + private final SearchDtoConverter, SearchHistory> filterConverter; private final SearchDtoConverter chartConverter; // private final SearchDtoConverter> panelResponseConverter; private final CurrentUserService currentUserService; + private final SearchHistoryRepository searchHistoryRepository; @Override @Transactional(readOnly = false) @@ -51,46 +62,54 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re // 현재 로그인한 유저 Member currentUser = currentUserService.getCurrentUser(); - // TODO : 추후 fast api 에서 불러오도록 수정해야 함 - FastNaturalLanguageResponseDTO.NaturalSearch response = fastApiService.getNaturalSearch( - FastNaturalLanguageRequestDTO.NaturalSearch.builder() - .query(request.getQuestion()) - .searchMode(request.getMode().getKr()) - .filters(new FastNaturalLanguageRequestDTO.NaturalSearchFilters()) - .build() - ); -// FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); + // fast api 호출 +// FastNaturalLanguageResponseDTO.NaturalSearch response = fastApiService.getNaturalSearch( +// FastNaturalLanguageRequestDTO.NaturalSearch.builder() +// .query(request.getQuestion()) +// .searchMode(request.getMode().getKr()) +// .filters(new FastNaturalLanguageRequestDTO.NaturalSearchFilters()) +// .build() +// ); + + // fast api에 요청보낼 requestDTO 생성 + MainSearchResponse response = fastApiService.getMainSearch(makeRequestDTO(request)); - FastNaturalLanguageResponseDTO.Data responseData = response.getData(); +// FastNaturalSearchResponseDTO.SearchResult response = new FastNaturalSearchResponseDTO.SearchResult(); +// FastNaturalLanguageResponseDTO.Data responseData = response.getData(); // id List 추출 - List panelIdList = responseData.getMatchedPanelIds(); +// List panelIdList = responseData.getMatchedPanelIds(); + List panelIdList = response.getPanels().stream() + .map(MainSearchResponse.PanelInfo::getPanelId) + .toList(); // DB 에서 Panel 목록 가져옴 List foundPanels = panelRepository.findPanelsWithRawDataByIds(panelIdList); // SearchHistory 데이터 생성 및 저장 - SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, responseData); + SearchHistory searchHistory = searchHistoryService.makeSearchHistory(request, response); +// SearchHistory searchHistory = searchHistoryRepository.findById(Long.valueOf(response.getSearchId())) +// .orElseThrow(() -> new ErrorHandler(SearchStatus.SEARCH_HISTORY_NOT_FOUND)); searchHistory.setMember(currentUser); // SearchResult.Summary 생성 - SearchResponseDTO.SearchResult.Summary summary = summaryConverter.requestToDto(responseData, foundPanels); + SearchResponseDTO.SearchResult.Summary summary = summaryConverter.requestToDto(response, foundPanels); // SearchResult.AppliedFilter 생성 - List appliedFiltersSummary = filterConverter.requestToDto(response, searchHistory); + List appliedFiltersSummary = filterConverter.requestToDto(null, searchHistory); // 차트 생성 - List charts = chartService.makeChart(responseData, searchHistory, foundPanels); - charts.forEach(searchHistory::addChart); // 연관관계 편의 메서드 호출 +// List charts = chartService.makeChart(new FastNaturalLanguageResponseDTO.Data(), searchHistory, foundPanels); // TODO : 서브서버에서 차트 받아서 적용하기 +// charts.forEach(searchHistory::addChart); // 연관관계 편의 메서드 호출 // 차트 변환 - Chart pieChart = charts.stream().filter(chart -> chart.getChartType() == ChartType.PIE) // 상단 차트 ( PIE 하나 )를 변환하여 생성 - .findFirst().orElse(null); - ChartDTO.Graph pie = chartConverter.requestToDto(null, pieChart); - - List graphs = charts.stream() // PIE를 제외한 차트를 변환하여 List 생성 - .filter(chart -> chart.getChartType() != ChartType.PIE) - .map(chart -> chartConverter.requestToDto(null, chart)) - .toList(); +// Chart pieChart = charts.stream().filter(chart -> chart.getChartType() == ChartType.PIE) // 상단 차트 ( PIE 하나 )를 변환하여 생성 +// .findFirst().orElse(null); +// ChartDTO.Graph pie = chartConverter.requestToDto(null, pieChart); +// +// List graphs = charts.stream() // PIE를 제외한 차트를 변환하여 List 생성 +// .filter(chart -> chart.getChartType() != ChartType.PIE) +// .map(chart -> chartConverter.requestToDto(null, chart)) +// .toList(); // 개별 응답 데이터 처리 및 반환 // SearchPanelDTO.PanelData panelData = panelResponseConverter.requestToDto(response, foundPanels); @@ -99,10 +118,37 @@ public SearchResponseDTO.SearchResult search(SearchRequestDTO.NaturalLanguage re .searchId(searchHistory.getId()) .summary(summary) .appliedFiltersSummary(appliedFiltersSummary) - .pie(pie) - .charts(graphs) + .pie(null) // TODO : fast api에서 받아서 적용하기 + .charts(null) // .panelData(panelData) // 개별 API로 분리 .build(); } + + // fast api에 보낼 요청 dto 생성 + private MainSearchRequest makeRequestDTO(SearchRequestDTO.NaturalLanguage request){ + + // 추천 쿼리 선택 시 제공되는 구조화된 검색 파라미터 + Map recommendedFilters = new HashMap<>(); + + // ui에서 필터를 직접 선택한 경우 + Map structuredFilters = new HashMap<>(); + List filters = filterRepository.findByIds(request.getFilters()); + for (Filter filter : filters) { + String key = filter.getType(); // type을 키로 설정 + String value = filter.getRawDataValue(); // rawDataValue를 value로 설정 + + structuredFilters.merge(key, value, (oldVal, newVal) -> oldVal + "," + newVal); + } + + return MainSearchRequest.builder() + .query(request.getQuestion()) + .searchParams(recommendedFilters) + .structuredFilters(structuredFilters) + .searchMode(request.getMode().name().toLowerCase()) + .limit(100) + .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 7ea6c8a..00c4158 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 @@ -12,6 +12,7 @@ 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.fastapi.dto.response.MainSearchResponse; 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; @@ -22,7 +23,6 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; @Service @@ -37,18 +37,29 @@ public class SearchHistoryServiceImpl implements SearchHistoryService { private final List keys = List.of("응답자ID-respondent_id", "성별-gender", "나이-age", "거주지-residence", "월소득-personal_income", "일치율-concordance_rate"); @Override - public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastNaturalLanguageResponseDTO.Data fastApiResponse) { + public SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, MainSearchResponse fastApiResponse) { + + List panelIds = fastApiResponse.getPanels().stream() + .map(MainSearchResponse.PanelInfo::getPanelId) + .toList(); // SearchHistory 데이터 생성 및 저장 - SearchHistory searchHistory = historyRepository.save( - SearchHistory.builder() - .date(LocalDate.now()) - .content(request.getQuestion()) - .panelIds(fastApiResponse.getMatchedPanelIds()) - .concordanceRate(fastApiResponse.getMatchScores()) - .charts(new ArrayList<>()) - .build() - ); + SearchHistory searchHistory = historyRepository.findById(Long.valueOf(fastApiResponse.getSearchId())) + .orElseThrow(() -> new ErrorHandler(SearchStatus.SEARCH_HISTORY_NOT_FOUND)); + +// SearchHistory searchHistory = historyRepository.save( +// SearchHistory.builder() +// .date(LocalDate.now()) +// .content(request.getQuestion()) +//// .panelIds(fastApiResponse.getMatchedPanelIds()) +//// .panelIds(panelIds) +// .panelIds(panelIds) +//// .concordanceRate(fastApiResponse.getMatchScores()) +// .concordanceRate(new ArrayList<>()) +//// .concordanceRate(null) +// .charts(new ArrayList<>()) +// .build() +// ); // SearchFilter 생성 SearchFilter searchFilter = filterService.makeFilter(request.getFilters(), 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 dd755e6..8d4bdd7 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 @@ -4,8 +4,9 @@ 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 DiffLens.back_end.global.fastapi.dto.response.MainSearchResponse; public interface SearchHistoryService { - SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, FastNaturalLanguageResponseDTO.Data fastApiResponse); + SearchHistory makeSearchHistory(SearchRequestDTO.NaturalLanguage request, MainSearchResponse fastApiResponse); SearchResponseDTO.EachResponses getEachResponses(Long searchHistoryId, Integer pageNum, Integer size); } diff --git a/src/main/java/DiffLens/back_end/global/aop/ApiRequestLogAspect.java b/src/main/java/DiffLens/back_end/global/aop/ApiRequestLogAspect.java index 6e5a057..a9283f7 100644 --- a/src/main/java/DiffLens/back_end/global/aop/ApiRequestLogAspect.java +++ b/src/main/java/DiffLens/back_end/global/aop/ApiRequestLogAspect.java @@ -2,12 +2,14 @@ import DiffLens.back_end.domain.members.entity.Member; import DiffLens.back_end.domain.members.service.auth.CurrentUserService; +import DiffLens.back_end.global.aop.annotations.LogExecutionTime; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -58,4 +60,26 @@ public Object logApiRequest(ProceedingJoinPoint jp) throws Throwable { } } + @Around("CommonPointCut.methodRuntimeEndpoints()") + public Object logExecutionTime(ProceedingJoinPoint jp) throws Throwable { + long start = System.currentTimeMillis(); + MethodSignature signature = (MethodSignature) jp.getSignature(); + String methodName = signature.toShortString(); + + // 어노테이션 가져오기 + LogExecutionTime annotation = signature.getMethod().getAnnotation(LogExecutionTime.class); + String label = annotation.value(); + + try { + Object result = jp.proceed(); + long end = System.currentTimeMillis(); + log.info("⏱️ [{}] {}: {}ms", label, methodName, (end - start)); + return result; + } catch (Throwable ex) { + long end = System.currentTimeMillis(); + log.error("💥 [{}] {} ({}ms) - {}", label, methodName, (end - start), ex.getMessage()); + throw ex; + } + } + } \ No newline at end of file diff --git a/src/main/java/DiffLens/back_end/global/aop/CommonPointCut.java b/src/main/java/DiffLens/back_end/global/aop/CommonPointCut.java index 973148a..1be492c 100644 --- a/src/main/java/DiffLens/back_end/global/aop/CommonPointCut.java +++ b/src/main/java/DiffLens/back_end/global/aop/CommonPointCut.java @@ -7,4 +7,8 @@ public class CommonPointCut { // API 호출 시 로그 찍도록 @RestController 어노테이션에 PointCut 설정 @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)") public void restControllerEndpoints() {} + + @Pointcut("@annotation(DiffLens.back_end.global.aop.annotations.LogExecutionTime)") + public void methodRuntimeEndpoints() {} + } diff --git a/src/main/java/DiffLens/back_end/global/aop/annotations/LogExecutionTime.java b/src/main/java/DiffLens/back_end/global/aop/annotations/LogExecutionTime.java new file mode 100644 index 0000000..c11c2d1 --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/aop/annotations/LogExecutionTime.java @@ -0,0 +1,10 @@ +package DiffLens.back_end.global.aop.annotations; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface LogExecutionTime { + String value() default "메서드 실행 시간"; +} diff --git a/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java b/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java index 5a386e1..1b83173 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiClient.java @@ -1,8 +1,12 @@ package DiffLens.back_end.global.fastapi; +import DiffLens.back_end.global.aop.annotations.LogExecutionTime; +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.Component; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; /** * FastAPI 서버에 실제 HTTP 요청을 보냄 @@ -11,9 +15,7 @@ @RequiredArgsConstructor public class FastApiClient { - private final WebClient webClient = WebClient.builder() - .baseUrl("https://ai.difflens.site") - .build(); + private final WebClient fastApiWebClient; /** * @@ -24,6 +26,7 @@ public class FastApiClient { * @param response body의 클래스 * */ + @LogExecutionTime("서브서버 호출 소요시간") public R sendRequest(FastApiRequestType type, T requestBody) { // 요청 타입이 맞지 않을 경우에 대한 예외 @@ -31,12 +34,19 @@ public R sendRequest(FastApiRequestType type, T requestBody) { throw new IllegalArgumentException("올바르지 않은 요청 타입 " + type.name()); // TODO : 에외처리... } - return webClient.post() - .uri(type.getUri()) - .bodyValue(requestBody) - .retrieve() - .bodyToMono((Class) type.getResponseType()) - .block(); + R block = null; + try{ + block = fastApiWebClient.post() + .uri(type.getUri()) + .bodyValue(requestBody) + .retrieve() + .bodyToMono((Class) type.getResponseType()) + .block(); + }catch (Exception e){ // fast api 호출 중 에러 발생시 예외 발생 + throw new ErrorHandler(ErrorStatus.SUB_SERVER_ERROR); + } + + return 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 index 43e0b69..5cd4c42 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiRequestType.java @@ -1,13 +1,7 @@ package DiffLens.back_end.global.fastapi; -import DiffLens.back_end.global.fastapi.dto.request.FastHomeRequestDTO; -import DiffLens.back_end.global.fastapi.dto.request.FastLibraryRequestDTO; -import DiffLens.back_end.global.fastapi.dto.request.FastNaturalLanguageRequestDTO; -import DiffLens.back_end.global.fastapi.dto.request.FastReSearchRequestDTO; -import DiffLens.back_end.global.fastapi.dto.response.FastHomeResponseDTO; -import DiffLens.back_end.global.fastapi.dto.response.FastNaturalLanguageResponseDTO; -import DiffLens.back_end.global.fastapi.dto.response.FastReSearchResponseDTO; -import DiffLens.back_end.global.fastapi.dto.response.FastLibraryCompareResponseDTO; +import DiffLens.back_end.global.fastapi.dto.request.*; +import DiffLens.back_end.global.fastapi.dto.response.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -17,7 +11,9 @@ public enum FastApiRequestType { NATURAL_SEARCH("/ai/search", FastNaturalLanguageRequestDTO.NaturalSearch.class, FastNaturalLanguageResponseDTO.NaturalSearch.class), - RE_SEARCH("/ai/re-search", FastReSearchRequestDTO.ReSearch.class, FastReSearchResponseDTO.ReSearch.class), + NATURAL_SEARCH2("/api/search/", MainSearchRequest.class, + MainSearchResponse.class), + RE_SEARCH("/api/re-search", FastReSearchRequestDTO.ReSearch.class, FastReSearchResponseDTO.ReSearch.class), RECOMMENDATIONS("/ai/recommendations", FastHomeRequestDTO.HomeRecommendRequest.class, FastHomeResponseDTO.HomeRecommend.class), COMPARE("/ai/compare", FastLibraryRequestDTO.LibraryCompare.class, diff --git a/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java b/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java index fade724..03c1ca0 100644 --- a/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java +++ b/src/main/java/DiffLens/back_end/global/fastapi/FastApiService.java @@ -3,9 +3,11 @@ import DiffLens.back_end.global.fastapi.dto.request.FastHomeRequestDTO; import DiffLens.back_end.global.fastapi.dto.request.FastNaturalLanguageRequestDTO; import DiffLens.back_end.global.fastapi.dto.request.FastLibraryRequestDTO; +import DiffLens.back_end.global.fastapi.dto.request.MainSearchRequest; import DiffLens.back_end.global.fastapi.dto.response.FastHomeResponseDTO; import DiffLens.back_end.global.fastapi.dto.response.FastNaturalLanguageResponseDTO; import DiffLens.back_end.global.fastapi.dto.response.FastLibraryCompareResponseDTO; +import DiffLens.back_end.global.fastapi.dto.response.MainSearchResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -24,6 +26,11 @@ public FastNaturalLanguageResponseDTO.NaturalSearch getNaturalSearch( return fastApiClient.sendRequest(FastApiRequestType.NATURAL_SEARCH, request); } + // 자연어 검색2 + public MainSearchResponse getMainSearch(MainSearchRequest request){ + return fastApiClient.sendRequest(FastApiRequestType.NATURAL_SEARCH2, request); + } + // 라이브러리 비교 public FastLibraryCompareResponseDTO.CompareResult compareLibraries(FastLibraryRequestDTO.LibraryCompare request) { return fastApiClient.sendRequest(FastApiRequestType.COMPARE, request); diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/request/MainSearchRequest.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/MainSearchRequest.java new file mode 100644 index 0000000..513e3fc --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/request/MainSearchRequest.java @@ -0,0 +1,34 @@ +package DiffLens.back_end.global.fastapi.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.*; + +import java.util.Map; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "메인 검색 요청 DTO") +public class MainSearchRequest { + + @Schema(description = "자연어 검색 쿼리 (예: '20대 남성 서울 거주 100명')", example = "30대 여성 ChatGPT 사용자 서울 거주") + private String query; + + @Schema(description = "추천 쿼리 선택 시 제공되는 구조화된 검색 파라미터", example = "{\"age_group\": \"30대\", \"gender\": \"여성\", \"region\": \"서울\", \"limit\": 150}") + private Map searchParams; + + @Schema(description = "UI에서 필터를 직접 선택한 경우", example = "{\"age_group\": \"20대\", \"occupation\": [\"전문직\", \"사무직\"], \"region\": \"경기\"}") + private Map structuredFilters; + + @Schema(description = "검색 모드: 'strict' (엄격) 또는 'flexible' (유연)", example = "flexible", defaultValue = "strict") + private String searchMode = "strict"; + + @Min(1) + @Max(1000) + @Schema(description = "반환할 최대 결과 수", example = "100", defaultValue = "100") + private int limit = 100; +} diff --git a/src/main/java/DiffLens/back_end/global/fastapi/dto/response/MainSearchResponse.java b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/MainSearchResponse.java new file mode 100644 index 0000000..1de14dd --- /dev/null +++ b/src/main/java/DiffLens/back_end/global/fastapi/dto/response/MainSearchResponse.java @@ -0,0 +1,74 @@ +package DiffLens.back_end.global.fastapi.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "메인 검색 응답 DTO") +public class MainSearchResponse { + + @Schema(description = "검색 결과 고유 ID") + @JsonProperty("search_id") + private String searchId; + + @Schema(description = "원본 쿼리 (자연어)") + private String query; + + @Schema(description = "검색된 패널 목록") + private List panels; + + @Schema(description = "총 검색 결과 수") + @JsonProperty("total_couont") + private int totalCount; + + @Schema(description = "사용된 검색 모드") + @JsonProperty("search_mode") + private String searchMode; + + @Schema(description = "적용된 필터") + @JsonProperty("applied_filters") + private Map appliedFilters; + + @Schema(description = "검색 방법: 'natural_language', 'recommendation', 'structured_filter'") + @JsonProperty("search_method") + private String searchMethod; + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + @Schema(description = "패널 정보 DTO") + public static class PanelInfo { + + @Schema(description = "패널 ID") + @JsonProperty("panel_id") + private String panelId; + + private Integer age; + private String gender; + private String region; + private String occupation; + @JsonProperty("marital_status") + private String maritalStatus; + @JsonProperty("phone_brand") + private String phoneBrand; + @JsonProperty("car_brand") + private String carBrand; + @JsonProperty("profile_summary") + private String profileSummary; + + @Schema(description = "해시태그 목록") + private List hashtags; + } + +} + 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 9eb922b..2dc0662 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 @@ -20,6 +20,7 @@ public enum ErrorStatus implements BaseErrorCode { PAGE_NO_INVALID(HttpStatus.BAD_REQUEST, "COMMON406", "페이지 번호는 1 이상 이어야 합니다."), PAGE_NO_EXCEED(HttpStatus.BAD_REQUEST, "COMMON407", "최대 페이지를 벗어났습니다."), + SUB_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON502", "서브서버 요청 중 오류가 발생했습니다.") ; 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 7676589..e8a4efa 100644 --- a/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/DiffLens/back_end/global/security/JwtAuthenticationFilter.java @@ -34,6 +34,12 @@ protected void doFilterInternal( throws ServletException, IOException { String token = resolveToken(request); + String uri = request.getRequestURI(); + + if (uri.equals("/auth/reissue")) { + filterChain.doFilter(request, response); + return; + } try { if (token != null) { @@ -61,6 +67,7 @@ protected void doFilterInternal( new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); SecurityContextHolder.getContext().setAuthentication(anonymousAuth); } + } catch (Exception ex) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); log.error("\uD83D\uDEA8 {}", ex.getMessage()); diff --git a/src/main/java/DiffLens/back_end/global/web/WebClientConfig.java b/src/main/java/DiffLens/back_end/global/web/WebClientConfig.java index 8466f55..a4149ac 100644 --- a/src/main/java/DiffLens/back_end/global/web/WebClientConfig.java +++ b/src/main/java/DiffLens/back_end/global/web/WebClientConfig.java @@ -1,5 +1,6 @@ package DiffLens.back_end.global.web; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @@ -13,6 +14,13 @@ public RestTemplate restTemplate() { return new RestTemplate(); } + @Bean + public WebClient fastApiWebClient(@Value("${fast-api.url}") String baseUrl) { + return WebClient.builder() + .baseUrl(baseUrl) + .build(); + } + @Bean(name = "googleTokenWebClient") public WebClient googleTokenWebClient(WebClient.Builder builder) { return builder.baseUrl("https://oauth2.googleapis.com").build(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d0c2341..cf60449 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,3 +36,6 @@ management: metrics: export: enabled: true + +fast-api: + url: ${FAST_API_URL}