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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.dreamypatisiel.devdevdev.domain.repository.techArticle;

import com.dreamypatisiel.devdevdev.domain.entity.TechArticle;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class TechArticleDto {
private final TechArticle techArticle;
private final Double score;

public static TechArticleDto of(TechArticle techArticle, Double score) {
return new TechArticleDto(techArticle, score);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleDto;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

public interface TechArticleRepositoryCustom {
Slice<TechArticle> findBookmarkedByMemberAndCursor(Pageable pageable, Long techArticleId, BookmarkSort bookmarkSort,
Member member);

SliceCustom<TechArticle> findTechArticlesByCursor(Pageable pageable, Long techArticleId, TechArticleSort techArticleSort,
Long companyId, String keyword, Float score);
SliceCustom<TechArticleDto> findTechArticlesByCursor(Pageable pageable, Long techArticleId, TechArticleSort techArticleSort,
Long companyId, String keyword, Double score);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkSort;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleDto;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
Expand Down Expand Up @@ -50,9 +52,9 @@ public Slice<TechArticle> findBookmarkedByMemberAndCursor(Pageable pageable, Lon
}

@Override
public SliceCustom<TechArticle> findTechArticlesByCursor(Pageable pageable, Long techArticleId,
public SliceCustom<TechArticleDto> findTechArticlesByCursor(Pageable pageable, Long techArticleId,
TechArticleSort techArticleSort, Long companyId,
String keyword, Float score
String keyword, Double score
) {
// 키워드가 있는 경우 FULLTEXT 검색, 없는 경우 일반 조회
if (StringUtils.hasText(keyword)) {
Expand All @@ -62,9 +64,9 @@ public SliceCustom<TechArticle> findTechArticlesByCursor(Pageable pageable, Long
}

// 키워드 검색
private SliceCustom<TechArticle> findTechArticlesByCursorWithKeyword(Pageable pageable, Long techArticleId,
public SliceCustom<TechArticleDto> findTechArticlesByCursorWithKeyword(Pageable pageable, Long techArticleId,
TechArticleSort techArticleSort, Long companyId,
String keyword, Float score
String keyword, Double score
) {
// FULLTEXT 검색 조건 생성
BooleanExpression titleMatch = Expressions.booleanTemplate(
Expand All @@ -87,19 +89,27 @@ private SliceCustom<TechArticle> findTechArticlesByCursorWithKeyword(Pageable pa
techArticle.contents, keyword
);

// 전체 스코어 계산 (제목 가중치 2배)
// 전체 스코어 계산 (제목 가중치 2배, 안전한 범위로 제한)
NumberTemplate<Double> totalScore = Expressions.numberTemplate(Double.class,
"({0} * 2.0) + {1}", titleScore, contentsScore
"(LEAST({0}, 100000) * 2.0) + LEAST({1}, 100000)", titleScore, contentsScore
);

List<TechArticle> contents = query.selectFrom(techArticle)
// TechArticle과 score를 함께 조회
List<Tuple> results = query.select(techArticle, totalScore)
.from(techArticle)
.where(titleMatch.or(contentsMatch))
.where(getCompanyIdCondition(companyId))
.where(getCursorConditionForKeywordSearch(techArticleSort, techArticleId, score, totalScore))
.orderBy(getOrderSpecifierForKeywordSearch(techArticleSort, totalScore), techArticle.id.desc())
.limit(pageable.getPageSize())
.fetch();

// Tuple을 TechArticleDto로 변환
List<TechArticleDto> contents = results.stream()
.map(result -> TechArticleDto.of(
result.get(techArticle), result.get(totalScore)))
.toList();

// 키워드 검색 결과 총 갯수
long totalElements = query.select(techArticle.count())
.from(techArticle)
Expand All @@ -111,16 +121,21 @@ private SliceCustom<TechArticle> findTechArticlesByCursorWithKeyword(Pageable pa
}

// 일반 조회
private SliceCustom<TechArticle> findTechArticlesByCursorWithoutKeyword(Pageable pageable, Long techArticleId,
private SliceCustom<TechArticleDto> findTechArticlesByCursorWithoutKeyword(Pageable pageable, Long techArticleId,
TechArticleSort techArticleSort, Long companyId
) {
List<TechArticle> contents = query.selectFrom(techArticle)
List<TechArticle> results = query.selectFrom(techArticle)
.where(getCursorConditionFromTechArticleSort(techArticleSort, techArticleId))
.where(companyId != null ? techArticle.company.id.eq(companyId) : null)
.where(getCompanyIdCondition(companyId))
.orderBy(techArticleSort(techArticleSort), techArticle.id.desc())
.limit(pageable.getPageSize())
.fetch();

// Tuple을 TechArticleDto로 변환
List<TechArticleDto> contents = results.stream()
.map(result -> TechArticleDto.of(result, null))
.toList();

// 기술블로그 총 갯수
long totalElements = query.select(techArticle.count())
.from(techArticle)
Expand Down Expand Up @@ -188,13 +203,13 @@ private OrderSpecifier<?> techArticleSort(TechArticleSort techArticleSort) {

// 키워드 검색을 위한 커서 조건 생성
private Predicate getCursorConditionForKeywordSearch(TechArticleSort techArticleSort, Long techArticleId,
Float score, NumberTemplate<Double> totalScore) {
Double score, NumberTemplate<Double> totalScore) {
if (ObjectUtils.isEmpty(techArticleId) || ObjectUtils.isEmpty(score)) {
return null;
}

// HIGHEST_SCORE(정확도순)인 경우 스코어 기반 커서 사용
if (techArticleSort == TechArticleSort.HIGHEST_SCORE) {
if (techArticleSort == TechArticleSort.HIGHEST_SCORE || ObjectUtils.isEmpty(techArticleSort)) {
return totalScore.lt(score.doubleValue())
.or(totalScore.eq(score.doubleValue())
.and(techArticle.id.lt(techArticleId)));
Expand All @@ -217,13 +232,13 @@ private Predicate getCursorConditionForKeywordSearch(TechArticleSort techArticle
private OrderSpecifier<?> getOrderSpecifierForKeywordSearch(TechArticleSort techArticleSort,
NumberTemplate<Double> totalScore) {
// HIGHEST_SCORE(정확도순)인 경우 스코어 기반 정렬
if (techArticleSort == TechArticleSort.HIGHEST_SCORE) {
if (techArticleSort == TechArticleSort.HIGHEST_SCORE || ObjectUtils.isEmpty(techArticleSort)) {
return totalScore.desc();
}

// 다른 정렬 방식인 경우 기존 정렬 사용
return Optional.ofNullable(techArticleSort)
.orElse(TechArticleSort.HIGHEST_SCORE).getOrderSpecifierByTechArticleSort();
.orElse(TechArticleSort.LATEST).getOrderSpecifierByTechArticleSort();
}

public BooleanExpression getCompanyIdCondition(Long companyId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.*;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
Expand Down Expand Up @@ -50,17 +51,19 @@ public GuestTechArticleService(TechArticlePopularScorePolicy techArticlePopularS
@Override
public Slice<TechArticleMainResponse> getTechArticles(Pageable pageable, Long techArticleId,
TechArticleSort techArticleSort, String keyword,
Long companyId, Float score, Authentication authentication) {
Long companyId, Double score, Authentication authentication) {
// 익명 사용자 호출인지 확인
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

// 기술블로그 조회
SliceCustom<TechArticle> techArticles = techArticleRepository.findTechArticlesByCursor(
SliceCustom<TechArticleDto> techArticles = techArticleRepository.findTechArticlesByCursor(
pageable, techArticleId, techArticleSort, companyId, keyword, score);

// 데이터 가공
List<TechArticleMainResponse> techArticlesResponse = techArticles.stream()
.map(TechArticleMainResponse::of)
.map(techArticle -> TechArticleMainResponse.of(
techArticle.getTechArticle(), techArticle.getScore()
))
.toList();

return new SliceCustom<>(techArticlesResponse, pageable, techArticles.hasNext(), techArticles.getTotalElements());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import com.dreamypatisiel.devdevdev.domain.entity.TechArticle;
import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend;
import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRecommendRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.*;
import com.dreamypatisiel.devdevdev.global.common.MemberProvider;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.*;
Expand Down Expand Up @@ -50,17 +47,17 @@ public MemberTechArticleService(TechArticlePopularScorePolicy techArticlePopular
@Override
public Slice<TechArticleMainResponse> getTechArticles(Pageable pageable, Long techArticleId,
TechArticleSort techArticleSort, String keyword,
Long companyId, Float score, Authentication authentication) {
Long companyId, Double score, Authentication authentication) {
// 회원 조회
Member member = memberProvider.getMemberByAuthentication(authentication);

// 기술블로그 조회
SliceCustom<TechArticle> techArticles = techArticleRepository.findTechArticlesByCursor(
SliceCustom<TechArticleDto> techArticles = techArticleRepository.findTechArticlesByCursor(
pageable, techArticleId, techArticleSort, companyId, keyword, score);

// 데이터 가공
List<TechArticleMainResponse> techArticlesResponse = techArticles.stream()
.map(techArticle -> TechArticleMainResponse.of(techArticle, member))
.map(techArticle -> TechArticleMainResponse.of(techArticle.getTechArticle(), member, techArticle.getScore()))
.toList();

return new SliceCustom<>(techArticlesResponse, pageable, techArticles.hasNext(), techArticles.getTotalElements());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

public interface TechArticleService {
Slice<TechArticleMainResponse> getTechArticles(Pageable pageable, Long techArticleId, TechArticleSort techArticleSort,
String keyword, Long companyId, Float score,
String keyword, Long companyId, Double score,
Authentication authentication);

TechArticleDetailResponse getTechArticle(Long techArticleId, String anonymousMemberId, Authentication authentication);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ResponseEntity<BasicResponse<Slice<TechArticleMainResponse>>> getTechArti
@RequestParam(required = false) Long techArticleId,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long companyId,
@RequestParam(required = false) Float score
@RequestParam(required = false) Double score
) {
TechArticleService techArticleService = techArticleServiceStrategy.getTechArticleService();
Authentication authentication = AuthenticationMemberUtils.getAuthentication();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ public class TechArticleMainResponse {
public final Long popularScore;
public final Boolean isLogoImage;
public final Boolean isBookmarked;
public final Float score;
public final Double score;

@Builder
private TechArticleMainResponse(Long id, String title, String contents, String author, CompanyResponse company,
LocalDate regDate, String thumbnailUrl, String techArticleUrl,
Long viewTotalCount, Long recommendTotalCount, Long commentTotalCount, Long popularScore,
Boolean isLogoImage, Boolean isBookmarked, Float score) {
Boolean isLogoImage, Boolean isBookmarked, Double score) {
this.id = id;
this.title = title;
this.contents = contents;
Expand Down Expand Up @@ -96,7 +96,7 @@ public static TechArticleMainResponse of(TechArticle techArticle) {
.build();
}

public static TechArticleMainResponse of(TechArticle techArticle, Member member, Float score) {
public static TechArticleMainResponse of(TechArticle techArticle, Member member, Double score) {
CompanyResponse companyResponse = CompanyResponse.from(techArticle.getCompany());
return TechArticleMainResponse.builder()
.id(techArticle.getId())
Expand All @@ -117,7 +117,7 @@ public static TechArticleMainResponse of(TechArticle techArticle, Member member,
.build();
}

public static TechArticleMainResponse of(TechArticle techArticle, Float score) {
public static TechArticleMainResponse of(TechArticle techArticle, Double score) {
CompanyResponse companyResponse = CompanyResponse.from(techArticle.getCompany());
return TechArticleMainResponse.builder()
.id(techArticle.getId())
Expand Down Expand Up @@ -146,8 +146,8 @@ private static String getThumbnailUrl(Url thumbnailUrl, CompanyResponse companyR
return thumbnailUrl.getUrl();
}

private static Float getValidScore(Float score) {
return Objects.isNull(score) || Float.isNaN(score) ? null : score;
private static Double getValidScore(Double score) {
return Objects.isNull(score) || Double.isNaN(score) ? null : score;
}

private static String truncateString(String contents, int maxLength) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private TechArticleMainResponse createTechArticleMainResponse(Long id, String th
String techArticleUrl, String title, String contents,
Long companyId, String companyName, String careerUrl, String officialImageUrl,
LocalDate regDate, String author, long recommendCount,
long commentCount, long viewCount, Boolean isBookmarked, Float score) {
long commentCount, long viewCount, Boolean isBookmarked, Double score) {
return TechArticleMainResponse.builder()
.id(id)
.thumbnailUrl(thumbnailUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void getTechArticlesByAnonymous() throws Exception {
TechArticleMainResponse response = createTechArticleMainResponse(
1L, "http://thumbnail.com", false, "http://article.com", "타이틀 1", "내용 1",
1L, "회사명", "http://career.com", "http://official.com", LocalDate.now(), "작성자",
10L, 5L, 100L, null, 10.0f
10L, 5L, 100L, null, 10.0
);

SliceCustom<TechArticleMainResponse> mockSlice = new SliceCustom<>(
Expand Down Expand Up @@ -130,7 +130,7 @@ void getTechArticlesByMember() throws Exception {
TechArticleMainResponse response = createTechArticleMainResponse(
1L, "http://thumbnail.com", false, "http://article.com", "타이틀 1", "내용 1",
1L, "회사명", "http://career.com", "http://official.com", LocalDate.now(), "작성자",
10L, 5L, 100L, true, 10.0f
10L, 5L, 100L, true, 10.0
);

SliceCustom<TechArticleMainResponse> mockSlice = new SliceCustom<>(
Expand Down Expand Up @@ -512,7 +512,7 @@ private TechArticleMainResponse createTechArticleMainResponse(Long id, String th
String techArticleUrl, String title, String contents,
Long companyId, String companyName, String careerUrl, String officialImageUrl,
LocalDate regDate, String author, long recommendCount,
long commentCount, long viewCount, Boolean isBookmarked, Float score) {
long commentCount, long viewCount, Boolean isBookmarked, Double score) {
return TechArticleMainResponse.builder()
.id(id)
.thumbnailUrl(thumbnailUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ private TechArticleMainResponse createTechArticleMainResponse(Long id, String th
String techArticleUrl, String title, String contents,
Long companyId, String companyName, String careerUrl, String officialImageUrl,
LocalDate regDate, String author, long recommendCount,
long commentCount, long viewCount, Boolean isBookmarked, Float score) {
long commentCount, long viewCount, Boolean isBookmarked, Double score) {
return TechArticleMainResponse.builder()
.id(id)
.thumbnailUrl(thumbnailUrl)
Expand Down
Loading