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
17 changes: 16 additions & 1 deletion src/docs/asciidoc/post-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ include::{snippetsDir}/deletePost/4/http-response.adoc[]

=== **5. 트렌딩 게시글 조회**

트랜딩 게시글 상세 조회 api 입니다.
트랜딩 게시글 조회 api 입니다.

==== Request
include::{snippetsDir}/loadTrendingPosts/1/http-request.adoc[]
Expand All @@ -211,3 +211,18 @@ include::{snippetsDir}/loadTrendingPosts/1/response-fields.adoc[]
---


=== **6. 트렌딩 핏더맨 조회**

트랜딩 핏더맨 조회 api 입니다.

==== Request
include::{snippetsDir}/loadTrendingMan/1/http-request.adoc[]

==== 성공 Response
include::{snippetsDir}/loadTrendingMan/1/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/loadTrendingMan/1/response-fields.adoc[]

---

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ftm.server.adapter.in.web.post.controller;

import com.ftm.server.adapter.in.web.post.dto.response.LoadTrendingManResponse;
import com.ftm.server.application.port.in.post.LoadTrendingManUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class GetTrendingManController {

private final LoadTrendingManUseCase loadTrendingManUseCase;

@GetMapping("/api/posts/trend/users")
public ResponseEntity<ApiResponse> getTrendingMan() {
List<LoadTrendingManResponse> result =
loadTrendingManUseCase.execute().stream()
.map(LoadTrendingManResponse::from)
.toList();
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessResponseCode.OK, result));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ftm.server.adapter.in.web.post.dto.response;

import com.ftm.server.application.vo.post.TrendingUserVo;
import lombok.Data;

@Data
public class LoadTrendingManResponse {
private final Integer ranking;
private final Long userId;
private final String userName;
private final String userImageUrl;

public static LoadTrendingManResponse from(TrendingUserVo vo) {
return new LoadTrendingManResponse(
vo.getRank(), vo.getUserId(), vo.getUserName(), vo.getUserImageUrl());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.ftm.server.adapter.out.cache;

import com.ftm.server.application.port.out.cache.LoadTrendingManWithCachePort;
import com.ftm.server.application.port.out.persistence.post.LoadPostWithBookmarkCountPort;
import com.ftm.server.application.port.out.persistence.user.LoadUserImagePort;
import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.query.FindUserImagesByIdsQuery;
import com.ftm.server.application.vo.post.TrendingUserVo;
import com.ftm.server.application.vo.post.UserWithPostCountVo;
import com.ftm.server.common.annotation.Adapter;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;

@Adapter
@RequiredArgsConstructor
@Slf4j
@CacheConfig(cacheManager = "trendingUsersCacheManager")
public class LoadTrendingManWithCacheAdapter implements LoadTrendingManWithCachePort {

private final LoadPostWithBookmarkCountPort loadPostPort;

private final LoadUserImagePort loadUserImagePort;

private final Integer N = 5;

@Override
public List<TrendingUserVo> loadTrendingMan() {

// 현재보다 1주일 이전에 작성된 게시물만 조회(북마크 조회수 포함) (예전 게시물은 포함x)
List<UserWithPostCountVo> userWithCount =
loadPostPort.loadAllPostsWithUserAndBookmarkCount(
FindPostsByCreatedDateQuery.of(LocalDate.now()));

// 1. 최대값 계산 (정규화를 위해)
long maxView = 1, maxLike = 1, maxScrap = 1;
for (UserWithPostCountVo post : userWithCount) {
maxView = Math.max(maxView, post.getViewCount());
maxLike = Math.max(maxLike, post.getLikeCount());
maxScrap = Math.max(maxScrap, post.getScrapCount());
}

// 2. 점수 계산 후 정렬
long finalMaxView = maxView;
long finalMaxLike = maxLike;
long finalMaxScrap = maxScrap;
List<UserWithPostCountVo> topN =
userWithCount.stream()
.sorted(
Comparator.comparingDouble(
p ->
-((double) p.getViewCount() / finalMaxView
+ (double) p.getLikeCount()
/ finalMaxLike
+ (double) p.getScrapCount()
/ finalMaxScrap)
/ 3.0))
.limit(N)
.toList();

// 3. 각 User 이미지 조회
List<Long> userIds = topN.stream().map(UserWithPostCountVo::getUserId).toList();

Map<Long, String> imageMap = new HashMap<>();
userIds.forEach(p -> imageMap.put(p, null));
loadUserImagePort
.loadUserImagesByUserIdIn(FindUserImagesByIdsQuery.of(userIds))
.forEach(userImage -> imageMap.put(userImage.getId(), userImage.getObjectKey()));

// 4. 결과 조합 (순위 부여)
return IntStream.range(0, topN.size())
.mapToObj(
i -> {
UserWithPostCountVo userWithPostCountVo = topN.get(i);
String imageKey = imageMap.get(userWithPostCountVo.getUserId());
return TrendingUserVo.of(
i + 1,
userWithPostCountVo.getUserId(),
userWithPostCountVo.getUserName(),
imageKey); // rank = i+1
})
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public class LoadTrendingPostsWithCacheAdapter implements LoadTrendingPostsWithC
@Cacheable(value = TRENDING_POSTS_CACHE_NAME, key = TRENDING_POSTS_CACHE_KEY_ALL)
public List<TrendingPostVo> loadTrendingPosts() {

log.info("오잉 캐시 실행 안됨!!");
// 현재보다 1주일 이전에 작성된 게시물만 조회(북마크 조회수 포함) (예전 게시물은 포함x)
List<PostWithBookmarkCountVo> rawPosts =
loadPostPort.loadAllPostsWithBookmarkCount(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
import com.ftm.server.application.port.out.persistence.post.*;
import com.ftm.server.application.query.*;
import com.ftm.server.application.vo.post.PostWithBookmarkCountVo;
import com.ftm.server.application.vo.post.UserWithPostCountVo;
import com.ftm.server.common.annotation.Adapter;
import com.ftm.server.common.exception.CustomException;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import com.ftm.server.domain.entity.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -296,6 +295,12 @@ public List<PostWithBookmarkCountVo> loadAllPostsWithBookmarkCount(
return postRepository.findAllPostsWithBookmarkCount(query);
}

@Override
public List<UserWithPostCountVo> loadAllPostsWithUserAndBookmarkCount(
FindPostsByCreatedDateQuery query) {
return postRepository.findAllPostsWithUserAndBookmarkCount(query);
}

@Override
public List<PostImage> loadRepresentativeImagesByPostIds(FindByIdsQuery query) {
return postImageRepository.findRepresentativeImagesByPostIdIn(query).stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public void deleteGroomingTestResultByUserList(
@Override
public List<String> deleteUserImageByUserList(DeleteUserImageByUserIdCommand command) {
List<String> imageKeyList =
userImageRepository.findAllByUserIdList(command.getUserIdList());
userImageRepository.findAllImagesByUserIdList(command.getUserIdList());
userImageRepository.deleteAllByUserIdList(command.getUserIdList());
return imageKeyList;
}
Expand Down Expand Up @@ -300,4 +300,11 @@ public Optional<Bookmark> loadBookmarkByUserIdAndPostId(
bookmarkRepository.findByUserIdAndPostId(query.getUserId(), query.getPostId());
return bookmarkJpaEntity.map(bookmarkMapper::toDomainEntity);
}

@Override
public List<UserImage> loadUserImagesByUserIdIn(FindUserImagesByIdsQuery query) {
return userImageRepository.findAllByUserIdList(query.getUserIds()).stream()
.map(userImageMapper::toDomainEntity)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.query.FindPostsByPagingQuery;
import com.querydsl.core.Tuple;
import java.util.List;
import org.springframework.data.domain.Slice;

Expand All @@ -11,4 +13,6 @@ public interface PostCustomRepository {
List<PostJpaEntity> findAllByDeletedBefore(FindPostByDeleteOptionQuery query);

Slice<PostJpaEntity> findAllByUserIdWithPaging(FindPostsByPagingQuery query);

List<Tuple> findAllByCreatedDateInOneWeekAndUserGrouping(FindPostsByCreatedDateQuery query);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.query.FindPostsByPagingQuery;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -51,4 +54,19 @@ public Slice<PostJpaEntity> findAllByUserIdWithPaging(FindPostsByPagingQuery que

return new SliceImpl<>(result, pageable, hasNext);
}

@Override
public List<Tuple> findAllByCreatedDateInOneWeekAndUserGrouping(
FindPostsByCreatedDateQuery query) {

LocalDateTime oneWeekAgo =
query.getDate().minusWeeks(1).atStartOfDay(); // 현재 기준 1주일 전 게시물만 조회

return queryFactory
.select(postJpaEntity.user, postJpaEntity.id.count())
.from(postJpaEntity)
.groupBy(postJpaEntity.user)
.where(postJpaEntity.createdAt.goe(oneWeekAgo))
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.vo.post.PostWithBookmarkCountVo;
import com.ftm.server.application.vo.post.UserWithPostCountVo;
import java.util.List;

public interface PostWithBookmarkCustomRepository {

List<PostWithBookmarkCountVo> findAllPostsWithBookmarkCount(FindPostsByCreatedDateQuery query);

List<UserWithPostCountVo> findAllPostsWithUserAndBookmarkCount(
FindPostsByCreatedDateQuery query);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.vo.post.PostWithBookmarkCountVo;
import com.ftm.server.application.vo.post.UserWithPostCountVo;
import com.ftm.server.domain.enums.UserRole;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDateTime;
Expand All @@ -22,7 +24,7 @@ public class PostWithBookmarkCustomRepositoryImpl implements PostWithBookmarkCus
public List<PostWithBookmarkCountVo> findAllPostsWithBookmarkCount(
FindPostsByCreatedDateQuery query) {

LocalDateTime twoWeeksAgo =
LocalDateTime oneWeekAgo =
query.getDate().minusWeeks(1).atStartOfDay(); // 현재 기준 1주일 전 게시물만 조회

return queryFactory
Expand All @@ -37,8 +39,46 @@ public List<PostWithBookmarkCountVo> findAllPostsWithBookmarkCount(
.from(postJpaEntity)
.leftJoin(bookmarkJpaEntity)
.on(bookmarkJpaEntity.post.eq(postJpaEntity))
.where(postJpaEntity.createdAt.goe(twoWeeksAgo))
.where(postJpaEntity.createdAt.goe(oneWeekAgo))
.where(postJpaEntity.isDeleted.eq(false)) // 삭제 되지 않은 것만 트렌딩 게시물에 포함하기
.groupBy(postJpaEntity.id)
.fetch();
}

@Override
public List<UserWithPostCountVo> findAllPostsWithUserAndBookmarkCount(
FindPostsByCreatedDateQuery query) {
LocalDateTime oneWeekAgo =
query.getDate().minusWeeks(1).atStartOfDay(); // 현재 기준 1주일 전 게시물만 조회

return queryFactory
.select(
Projections.constructor(
UserWithPostCountVo.class,
postJpaEntity.user.id,
postJpaEntity.user.nickname,
postJpaEntity.viewCount.sum(),
postJpaEntity.likeCount.sum(),
bookmarkJpaEntity.id.count()))
.from(postJpaEntity)
.leftJoin(bookmarkJpaEntity)
.on(bookmarkJpaEntity.post.eq(postJpaEntity))
.where(
postJpaEntity
.isDeleted
.eq(false)
.and(
postJpaEntity.createdAt.goe(
oneWeekAgo))) // 삭제되지 않은 1주일 이내 게시물만 포함.
.where(
postJpaEntity
.user
.isDeleted
.eq(false)
.and(
postJpaEntity.user.role.eq(
UserRole.USER))) // 삭제되지 않은 일반 유저만 포함
.groupBy(postJpaEntity.user.id, postJpaEntity.user.nickname)
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ public interface UserImageRepository extends JpaRepository<UserImageJpaEntity, L
void deleteAllByUserIdList(@Param("userIds") List<Long> userIds);

@Query("SELECT ui.objectKey FROM UserImageJpaEntity ui WHERE ui.user.id in (:userIds)")
List<String> findAllByUserIdList(@Param("userIds") List<Long> userIds);
List<String> findAllImagesByUserIdList(@Param("userIds") List<Long> userIds);

@Query("SELECT ui FROM UserImageJpaEntity ui WHERE ui.user.id in (:userIds)")
List<UserImageJpaEntity> findAllByUserIdList(@Param("userIds") List<Long> userIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.in.post;

import com.ftm.server.application.vo.post.TrendingUserVo;
import com.ftm.server.common.annotation.UseCase;
import java.util.List;

@UseCase
public interface LoadTrendingManUseCase {
List<TrendingUserVo> execute();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.out.cache;

import com.ftm.server.application.vo.post.TrendingUserVo;
import com.ftm.server.common.annotation.Port;
import java.util.List;

@Port
public interface LoadTrendingManWithCachePort {
List<TrendingUserVo> loadTrendingMan();
}
Loading
Loading