From 96ffb4181ea1c5a896fa7d7158c84fa10456fe5e Mon Sep 17 00:00:00 2001 From: jonghun Date: Mon, 9 Sep 2024 11:55:07 +0900 Subject: [PATCH] =?UTF-8?q?#119=20fix:=20=EC=A3=BC=EA=B0=84=20=EB=B2=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=BA=90?= =?UTF-8?q?=EC=8B=B1=20=EC=A0=81=EC=9A=A9,=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=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 - 테스트 코드 작성 - RedisCache 적용 --- .../post/implement/PostServiceImpl.java | 349 ------------------ .../domain/post/service/PostServiceImpl.java | 306 +++++++++++++++ .../system/config/RedisCacheConfig.java | 29 ++ .../domain/post/PostRepositoryTest.java | 53 +++ .../domain/post/PostServiceTest.java | 50 +++ 5 files changed, 438 insertions(+), 349 deletions(-) delete mode 100644 src/main/java/com/seoultech/synergybe/domain/post/implement/PostServiceImpl.java create mode 100644 src/main/java/com/seoultech/synergybe/domain/post/service/PostServiceImpl.java create mode 100644 src/main/java/com/seoultech/synergybe/system/config/RedisCacheConfig.java create mode 100644 src/test/java/com/seoultech/synergybe/domain/post/PostRepositoryTest.java create mode 100644 src/test/java/com/seoultech/synergybe/domain/post/PostServiceTest.java diff --git a/src/main/java/com/seoultech/synergybe/domain/post/implement/PostServiceImpl.java b/src/main/java/com/seoultech/synergybe/domain/post/implement/PostServiceImpl.java deleted file mode 100644 index 9a3e5b3..0000000 --- a/src/main/java/com/seoultech/synergybe/domain/post/implement/PostServiceImpl.java +++ /dev/null @@ -1,349 +0,0 @@ -//package com.seoultech.synergybe.domain.post.implement; -// -//import com.fasterxml.jackson.core.JsonProcessingException; -//import com.fasterxml.jackson.core.type.TypeReference; -//import com.fasterxml.jackson.databind.ObjectMapper; -//import com.seoultech.synergybe.domain.common.PageInfo; -//import com.seoultech.synergybe.domain.common.idgenerator.IdGenerator; -//import com.seoultech.synergybe.domain.common.idgenerator.IdPrefix; -//import com.seoultech.synergybe.domain.common.paging.ListResponse; -//import com.seoultech.synergybe.domain.follow.service.FollowService; -//import com.seoultech.synergybe.domain.post.Post; -//import com.seoultech.synergybe.domain.post.presentation.dto.request.CreatePostRequest; -//import com.seoultech.synergybe.domain.post.presentation.dto.request.UpdatePostRequest; -//import com.seoultech.synergybe.domain.post.presentation.dto.response.GetListPostResponse; -//import com.seoultech.synergybe.domain.post.presentation.dto.response.GetPostResponse; -//import com.seoultech.synergybe.domain.post.exception.PostBadRequestException; -//import com.seoultech.synergybe.domain.post.exception.PostNotFoundException; -//import com.seoultech.synergybe.domain.post.data.PostJpaRepository; -//import com.seoultech.synergybe.domain.postlike.service.PostLikeService; -//import com.seoultech.synergybe.domain.user.User; -//import jakarta.persistence.criteria.CriteriaBuilder; -//import jakarta.persistence.criteria.CriteriaQuery; -//import jakarta.persistence.criteria.Predicate; -//import jakarta.persistence.criteria.Root; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.jpa.domain.Specification; -//import org.springframework.stereotype.Service; -//import com.seoultech.synergybe.domain.user.service.UserService; -//import org.springframework.transaction.annotation.Transactional; -//import org.springframework.web.client.RestTemplate; -// -//import java.util.*; -//import java.util.stream.Collectors; -//import java.util.stream.Stream; -// -//@Slf4j -//@Transactional -//@Service -//@RequiredArgsConstructor -//public class PostServiceImpl { -// private final PostJpaRepository postJpaRepository; -// private final FollowService followService; -// private final PostLikeService postLikeService; -// private final IdGenerator idGenerator; -// private final UserService userService; -// private final PostReader postReader; -// private final PostValidator postValidator; -//// private final ImageService imageService; -// -// /** -// * 게시글 객체 생성 비즈니스 로직은 ? -// * - 게시글 생성 -// * - 게시글 저장 객체에 저장을 위임 -// * 필요한 인자를 어떻게 정리할 수 있을까 ? -// * Post 생성에 필요한 인자들 -// * -// * @param userId -// * @param request -// * @return -// */ -//// @Override -// public GetPostResponse createPost(String userId, CreatePostRequest request) { -// /** -// * 게시글 객체 생성 비즈니스 로직은 ? -// * - 게시글 생성 -// * - 게시글 저장 객체에 저장을 위임 -// * - -// */ -// -// User user = userService.getUser(userId); -// if (request.files() == null) { -// log.info(">> getfiles is null"); -// String postId = idGenerator.generateId(IdPrefix.POST); -// Post post = Post.builder() -// .id(postId) -// .title(request.title()) -// .content(request.content()) -// .user(user) -// .build(); -// Post savedPost = postJpaRepository.save(post); -// -// return GetPostResponse.builder() -// .postId(savedPost.getId()) -// .build(); -//// } else { -//// log.info(">> getfiles is NOT NULL"); -//// List files = request.files(); -////// List images = imageService.storeImageList(files); -//// -////// Post post = request.toEntity(user, images); -//// Post post = request.toEntity(user); -//// Post savedPost = postRepository.save(post); -////// List imagesUrl = imageService.getImageUrlByPostId(savedPost.getId()); -//// -////// return PostResponse.from(savedPost, imagesUrl); -//// return GetPostResponse.from(savedPost); -// } -// return GetPostResponse.builder().build(); -// } -// -//// @Transactional -//// public GetPostResponse updatePost(String userId, UpdatePostRequest request) { -//// // todo -//// // user 검증 -//// User user = userService.getUser(userId); -//// Post post = findPostById(request.postId()); -//// -//// postValidator.validateUser(user, post); -//// validateUser(user, post); -//// post.updatePost(request.title(), request.content()); -////// List imagesUrl = imageService.getImageUrlByPostId(request.getPostId()); -//// -////// return PostResponse.from(updatedPost, imagesUrl); -//// return GetPostResponse.builder() -//// .postId(post.getId()) -//// .build(); -//// } -// -// private void validateUser(User user, Post post) { -// if (!post.getUser().equals(user)) { -// throw new PostBadRequestException("인증되지 않은 유저입니다."); -// } -// } -// -//// public void deletePost(String userId, String postId) { -//// Post post = findPostById(postId); -//// User user = userService.getUser(userId); -//// postValidator.validateUser(user, post); -//// validateUser(user, post); -//// postJpaRepository.delete(post); -//// } -// -// -// -// public List findAllByFollowingIdAndEndId(String userId, Long end) { -// return postJpaRepository.findAllByIdsAndEndId(userId, end); -// } -// -// -//// public GetListPostResponse getMyLikedPostList(String userId) { -//// List postList = postRepository.findAllByLikeAndDate(); -//// -//// -//// return PostMapperEntityToDto.postListToResponse(postList); -//// } -// -//// public GetListPostResponse getPostRecentList(Long offset) { -////// List posts = postRepository.findAllByEndId(end); -//// -//// // offset은 시작 지점 -//// // 0부터 시작하며 다음 요청시마다 10씩 증가해야함 -//// List posts = postJpaRepository.findAllByCreateAtAndLimit(offset); -//// int totalCount = postJpaRepository.countTotalPostSize(); -//// -//// boolean hasNext; -//// int pageSize = 10; -//// -//// if (totalCount > pageSize + offset) { -//// hasNext = true; -//// } else { -//// hasNext = false; -//// } -//// // todo -//// // 썸네일이 없을 경우 없는채로 처리가 되어야 함 -//// -//// return PostMapperEntityToDto.postListToResponse(posts); -//// } -// -// -// -// public ListResponse getPostListByUser(String userId) { -// List posts = postJpaRepository.findAllByUserId(userId); -// ListResponse getPostResponseListResponse = new ListResponse(posts); -// -// return getPostResponseListResponse; -// } -// -// -// -//// @Transactional(readOnly = true) -////// @Cacheable(value = "posts", key = "'weekBestPostList'", cacheManager = "contentCacheManager") -//// public ListPostResponse getWeekBestPostList() { -//// List posts = postRepository.findAllByLikeAndDate(); -//// -//// return ListPostResponse.from(GetPostResponse.from(posts)); -//// } -// -// public ListResponse getFeed(Long end, User user) { -// List followingIds = followService.findFollowingIdsByUserId(user.getId()); -// log.info("followingIds Size{}",followingIds.size()); -// List allPosts = new ArrayList<>(); -// -// -// for (String id : followingIds) { -// // 각 팔로잉 유저 기준 10개씩 fetch -// List postList = this.findAllByFollowingIdAndEndId(id, end); -// allPosts.addAll(postList); -// } -// log.info("post size {}",allPosts.size()); -//// Object to Stream : 리스트 내에서 createAt 기준으로 내림차순 정렬을 진행합니다 -// Stream sortedDescPostStream = allPosts.stream().sorted(Comparator.comparing(Post::getId).reversed()); -// // Stream to List -// List sortedDescPostList = sortedDescPostStream.collect(Collectors.toList()); -// -// // 리스트 크기 계산 -// int totalSize = sortedDescPostList.size(); -// log.info("{}", totalSize); -// int lastTenElements = 10; -// -// // 가장 마지막 10개 원소 fetch -// List lastTenPosts = sortedDescPostList.subList(Math.max(totalSize - lastTenElements, 0), totalSize); -// -// boolean isNext; -// int pageSize = 10; -// -// if (totalSize > pageSize) { -// isNext = true; -// } else { -// isNext = false; -// } -// -// ListResponse getPostResponseListResponse = new ListResponse(lastTenPosts); -// -// return getPostResponseListResponse; -// } -// -// public Page searchAllPosts(String keyword, Pageable pageable) { -// // query 생성 -// Specification spec = this.search(keyword); -// -// Page posts = postJpaRepository.findAll(spec, pageable); -// // 위에서 post를 바로 images url을 넣어서 전달해야함 -// -// -// return posts; -// } -// -// public Specification search(String keyword) { -// return new Specification() { -// -// @Override -// public Predicate toPredicate(Root postRoot, CriteriaQuery query, CriteriaBuilder cb) { -// query.distinct(true); -// -// // Post table과 User table을 Left join 수행 -//// Join userJoin = postRoot.join("user", JoinType.LEFT); -// -// try { -// return cb.or( -// //join한 table을 통해 authorname 얻음 -// cb.like(postRoot.get("title"), "%" + keyword + "%"), -// cb.like(postRoot.get("content"), "%" + keyword + "%"), -// cb.like(postRoot.get("authorName"), "%" + keyword + "%") -// ); -// } catch (Exception e) { -// log.error("search toPredicate Error {}", e.getMessage()); -// throw new PostNotFoundException("존재하지 않는 게시글입니다."); -// } -// } -// }; -// } -// -// public ListResponse getRecommendPostList(User user, Long end) { -// try { -// log.info("get recommend post list start"); -// String userId = user.getId(); -// log.info("user Id {}", userId); -// -// RestTemplate restTemplate = new RestTemplate(); -// log.info("rest template new"); -// String fastApiUrl = "http://fastapi:8000"; // 컨테이너 이름과 포트 -// String response = restTemplate.getForObject(fastApiUrl + "/recommend/posts/" + userId, String.class); -// -// log.info("Response from FastAPI: {}", response); -// -// List postIds = this.extractIds(response); -// -// // 빈 배열일 경우 빈 배열 리턴 -//// if (postIds.isEmpty()) { -//// List posts = new ArrayList<>(); -//// return ListPostResponse.from(GetPostResponse.fromEmpty(posts)); -//// } -// -// // end 기준 end ~ end + 10 순서에 있는 게시글 가져오기 -// int startIdx = end.intValue(); -// int endIdx = Math.min(startIdx + 10, postIds.size()); -// -// List result = postIds.subList(startIdx, endIdx); -// log.info(">> postIds result {} ", result); -// -// List posts = postJpaRepository.findAllByIdInOrderByListOrder(result); -// -// ListResponse getPostResponseListResponse = new ListResponse(posts); -// -// return getPostResponseListResponse; -// } catch (Exception e) { -// log.error(">> 추천 게시글 가져오기 실패 {}", e.getMessage()); -// throw new PostNotFoundException("존재하지 않는 게시글입니다."); -// } -// } -// -// private List extractIds(String response) { -// try { -// // 받은 JSON 응답을 자바 리스트로 파싱 -// ObjectMapper objectMapper = new ObjectMapper(); -// log.error(">> http cliend response body {}", response); -// -// return objectMapper.readValue(response, new TypeReference>() {}); -// } catch (JsonProcessingException e) { -// log.error(">> 객체 변환 실패 {}", e.getMessage()); -// throw new RuntimeException(e); -// } -// } -// -//// public GetPostResponse getPost(String postId) { -//// Post post = findPostById(postId); -//// List commentResponses = post.getComments().stream() -//// .map(comment -> new GetCommentResponse( -//// comment.getId(), -//// comment.getUser().getId(), -//// comment.getPost().getId(), -//// comment.getComment().getContent(), -//// comment.getUpdateAt() -//// )) -//// .toList(); -//// -//// return GetPostResponse.builder() -//// .postId(post.getId()) -//// .title(post.getTitle().getTitle()) -//// .content(post.getContent().getContent()) -//// .userId(post.getUser().getId()) -//// .likes(post.getLikes().size()) -//// .authorName(post.getAuthorName().getAuthorName()) -//// .createAt(post.getCreateAt()) -//// .updateAt(post.getUpdateAt()) -//// .commentList(commentResponses) -//// .build(); -//// } -// -// -//// public GetListPostResponse getWeekBestPostList() { -//// List postList = postJpaRepository.findAllByMostLikedAndRecentOneWeek(); -//// PageInfo pageInfo = PageInfo.of(postList.size()); -//// return new GetListPostResponse(postList, pageInfo); -//// } -//} diff --git a/src/main/java/com/seoultech/synergybe/domain/post/service/PostServiceImpl.java b/src/main/java/com/seoultech/synergybe/domain/post/service/PostServiceImpl.java new file mode 100644 index 0000000..4adca3f --- /dev/null +++ b/src/main/java/com/seoultech/synergybe/domain/post/service/PostServiceImpl.java @@ -0,0 +1,306 @@ +package com.seoultech.synergybe.domain.post.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.seoultech.synergybe.domain.comment.dto.response.GetCommentResponse; +import com.seoultech.synergybe.domain.common.generator.IdGenerator; +import com.seoultech.synergybe.domain.common.generator.IdPrefix; +import com.seoultech.synergybe.domain.common.generator.TokenGenerator; +import com.seoultech.synergybe.domain.common.paging.ListResponse; +import com.seoultech.synergybe.domain.follow.service.FollowService; +import com.seoultech.synergybe.domain.post.Post; +import com.seoultech.synergybe.domain.post.controller.dto.request.CreatePostRequest; +import com.seoultech.synergybe.domain.post.controller.dto.request.UpdatePostRequest; +import com.seoultech.synergybe.domain.post.controller.dto.response.GetPostResponse; +import com.seoultech.synergybe.domain.post.exception.PostBadRequestException; +import com.seoultech.synergybe.domain.post.exception.PostNotFoundException; +import com.seoultech.synergybe.domain.post.repository.PostRepository; +import com.seoultech.synergybe.domain.postlike.service.PostLikeService; +import com.seoultech.synergybe.domain.user.User; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import com.seoultech.synergybe.domain.user.service.UserService; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import org.springframework.cache.annotation.Cacheable; + + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +@Transactional(readOnly = true) +@Service +@RequiredArgsConstructor +public class PostServiceImpl implements PostService { + private final PostRepository postRepository; + private final FollowService followService; + private final PostLikeService postLikeService; + private final IdGenerator idGenerator; + private final TokenGenerator tokenGenerator; + private final UserService userService; + private final PostValidator postValidator; +// private final ImageService imageService; + + /** + * - 게시글 생성 + * - 게시글 저장 객체에 저장을 위임 + * Post 생성에 필요한 인자들 + * + * @param userToken + * @param request + * @return + */ + @Override + @Transactional + public String createPost(String userToken, CreatePostRequest request) { + User user = userService.getUserByToken(userToken); + if (request.files() != null) { + throw new IllegalArgumentException("file은 입력받지 않습니다."); + } + Long postId = idGenerator.generateId(); + String postToken = tokenGenerator.generateToken(IdPrefix.POST); + Post post = Post.builder() + .id(postId) + .postToken(postToken) + .title(request.title()) + .content(request.content()) + .user(user) + .build(); + postRepository.save(post); + + return post.getPostToken(); + } + + @Override + @Transactional + public String updatePost(String userToken, UpdatePostRequest request) { + User user = userService.getUserByToken(userToken); + Post post = postRepository.findByPostToken(request.postToken()); + postValidator.validateUser(user, post); + post.updatePost(request.title(), request.content()); + return post.getPostToken(); + } + + private void validateUser(User user, Post post) { + if (!post.getUser().equals(user)) { + throw new PostBadRequestException("인증되지 않은 유저입니다."); + } + } + + @Override + @Transactional + public void deletePost(String userToken, String postToken) { + Post post = postRepository.findByPostToken(postToken); + User user = userService.getUserByToken(userToken); + postValidator.validateUser(user, post); + postRepository.delete(post); + } + + @Override + public ListResponse getMyLikedPostList(String userToken) { + Long userId = userService.getUserByToken(userToken).getId(); + List postList = postRepository.findMyLikedPostList(userId); + + return PostMapperEntityToDto.postListToResponse(postList); + } + + @Override + public ListResponse getRecentPostList(String offsetToken) { + // offset은 시작 지점 + // 0부터 시작하며 다음 요청시마다 10씩 증가해야함 + Post offsetPost = postRepository.findByPostToken(offsetToken); + Long offset = offsetPost.getId(); + List posts = postRepository.findAllByCreateAtAndLimit(offset); + long totalCount = postRepository.countSize(); + + boolean hasNext; + int pageSize = 10; + + if (totalCount > pageSize + offset) { + hasNext = true; + } else { + hasNext = false; + } + + return PostMapperEntityToDto.postListToResponse(posts, hasNext); + } + + @Override + public ListResponse getPostListByUser(String userToken) { + Long userId = userService.getUserByToken(userToken).getId(); + List posts = postRepository.findAllByUserId(userId); + + return PostMapperEntityToDto.postListToResponse(posts); + } + + @Transactional(readOnly = true) + @Cacheable(value = "Post", cacheManager = "testCacheManager") + public ListResponse getWeekBestPostList() { + List posts = postRepository.findAllByLikeAndDate(); + + return PostMapperEntityToDto.postListToResponse(posts); + } + + @Override + public ListResponse getFeed(String offsetToken, String userToken) { + List followingIds = followService.getFollowingIdList(userToken); + List allPosts = new ArrayList<>(); + + for (Long userId : followingIds) { + // 각 팔로잉 유저 기준 10개씩 fetch + List postList = findAllByFollowingIdAndEndId(userId, offsetToken); + allPosts.addAll(postList); + } + log.info("post size {}",allPosts.size()); + // 리스트 내에서 createAt 기준으로 내림차순 정렬을 진행합니다 + Stream sortedDescPostStream = allPosts.stream().sorted(Comparator.comparing(Post::getId).reversed()); + // Stream to List + List sortedDescPostList = sortedDescPostStream.toList(); + + // 리스트 크기 계산 + int totalSize = sortedDescPostList.size(); + log.info("{}", totalSize); + int lastTenElements = 10; + + // 가장 마지막 10개 원소 fetch + List lastTenPosts = sortedDescPostList.subList(Math.max(totalSize - lastTenElements, 0), totalSize); + + boolean isNext; + int pageSize = 10; + + if (totalSize > pageSize) { + isNext = true; + } else { + isNext = false; + } + + return PostMapperEntityToDto.postListToResponse(lastTenPosts, isNext); + } + + @Override + public Page searchAllPosts(String keyword, Pageable pageable) { + Specification spec = search(keyword); + Page posts = postRepository.findAll(spec, pageable); + + return posts; + } + + public Specification search(String keyword) { + return new Specification() { + @Override + public Predicate toPredicate(Root postRoot, CriteriaQuery query, CriteriaBuilder cb) { + query.distinct(true); + + // Post table과 User table을 Left join 수행 +// Join userJoin = postRoot.join("user", JoinType.LEFT); + + try { + return cb.or( + //join한 table을 통해 authorname 얻음 + cb.like(postRoot.get("title"), "%" + keyword + "%"), + cb.like(postRoot.get("content"), "%" + keyword + "%"), + cb.like(postRoot.get("authorName"), "%" + keyword + "%") + ); + } catch (Exception e) { + log.error("search toPredicate Error {}", e.getMessage()); + throw new PostNotFoundException("존재하지 않는 게시글입니다."); + } + } + }; + } + + public ListResponse getRecommendPostList(String userToken, String postToken) { + try { + log.info("get recommend post list start"); + Long userId = userService.getUserByToken(userToken).getId(); + log.info("user Id {}", userId); + + Long end = postRepository.findByPostToken(postToken).getId(); + RestTemplate restTemplate = new RestTemplate(); + + log.info("rest template new"); + String fastApiUrl = "http://fastapi:8000"; // 컨테이너 이름과 포트 + String response = restTemplate.getForObject(fastApiUrl + "/recommend/posts/" + userId, String.class); + + log.info("Response from FastAPI: {}", response); + + List postIds = this.extractIds(response); + + // 빈 배열일 경우 빈 배열 리턴 + if (postIds.isEmpty()) { + List posts = new ArrayList<>(); + return PostMapperEntityToDto.postListToResponse(posts); + } + + // end 기준 end ~ end + 10 순서에 있는 게시글 가져오기 + int startIdx = end.intValue(); + int endIdx = Math.min(startIdx + 10, postIds.size()); + + List result = postIds.subList(startIdx, endIdx); + log.info(">> postIds result {} ", result); + + List posts = postRepository.findAllByIdInOrderByListOrder(result); + + ListResponse getPostResponseListResponse = new ListResponse(posts); + + return getPostResponseListResponse; + } catch (Exception e) { + log.error(">> 추천 게시글 가져오기 실패 {}", e.getMessage()); + throw new PostNotFoundException("존재하지 않는 게시글입니다."); + } + } + + private List extractIds(String response) { + try { + // 받은 JSON 응답을 자바 리스트로 파싱 + ObjectMapper objectMapper = new ObjectMapper(); + log.error(">> http cliend response body {}", response); + + return objectMapper.readValue(response, new TypeReference>() {}); + } catch (JsonProcessingException e) { + log.error(">> 객체 변환 실패 {}", e.getMessage()); + throw new RuntimeException(e); + } + } + + @Override + public GetPostResponse getPost(String postToken) { + Post post = postRepository.findByPostToken(postToken); + List commentResponses = post.getComments().stream() + .map(comment -> new GetCommentResponse( + comment.getCommentToken(), + comment.getUser().getUserToken(), + comment.getPost().getPostToken(), + comment.getComment().getContent(), + comment.getUpdateAt() + )) + .toList(); + + return GetPostResponse.builder() + .postToken(post.getPostToken()) + .title(post.getTitle().getTitle()) + .content(post.getContent().getContent()) + .userToken(post.getUser().getUserToken()) + .likes(post.getLikes().size()) + .authorName(post.getAuthorName().getAuthorName()) + .createAt(post.getCreateAt()) + .updateAt(post.getUpdateAt()) + .commentList(commentResponses) + .build(); + } + + private List findAllByFollowingIdAndEndId(Long userId, String offsetToken) { + return postRepository.findAllByIdsAndEndId(userId, offsetToken); + } +} diff --git a/src/main/java/com/seoultech/synergybe/system/config/RedisCacheConfig.java b/src/main/java/com/seoultech/synergybe/system/config/RedisCacheConfig.java new file mode 100644 index 0000000..3ae5652 --- /dev/null +++ b/src/main/java/com/seoultech/synergybe/system/config/RedisCacheConfig.java @@ -0,0 +1,29 @@ +package com.seoultech.synergybe.system.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisCacheConfig { + @Bean + public CacheManager testCacheManager(RedisConnectionFactory cf) { + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) + .entryTtl(Duration.ofMinutes(3L)); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build(); + } + +} diff --git a/src/test/java/com/seoultech/synergybe/domain/post/PostRepositoryTest.java b/src/test/java/com/seoultech/synergybe/domain/post/PostRepositoryTest.java new file mode 100644 index 0000000..2cd5aee --- /dev/null +++ b/src/test/java/com/seoultech/synergybe/domain/post/PostRepositoryTest.java @@ -0,0 +1,53 @@ +package com.seoultech.synergybe.domain.post; + +import com.seoultech.synergybe.domain.common.CustomPasswordEncoder; +import com.seoultech.synergybe.domain.post.repository.PostRepository; +import com.seoultech.synergybe.domain.user.User; +import com.seoultech.synergybe.domain.user.repository.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +class PostRepositoryTest { + @Autowired + private PostRepository postRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CustomPasswordEncoder passwordEncoder; + + @DisplayName("offset 기준 그 다음의 limit 개수 까지 post를 조회한다.") + @Test + void findAllByCreateAtAndLimit() { + int limit = 20; + long totalSize = 30L; + long offset = 20L; + long initialId = 0L; + int lastIndex = limit - 1; + User user = User.builder().id(1000L).userToken("token").passwordEncoder(passwordEncoder).email("as@gmail.com").password("password").name("name").major("major").build(); + userRepository.save(user); + + // 0 - 29 + for (long i=initialId; i response = postRepository.findAllByCreateAtAndLimit(offset); + + assertThat(response).hasSize(limit); + assertThat(response.get(lastIndex).getId()).isEqualTo(initialId); + } + + + +} diff --git a/src/test/java/com/seoultech/synergybe/domain/post/PostServiceTest.java b/src/test/java/com/seoultech/synergybe/domain/post/PostServiceTest.java new file mode 100644 index 0000000..1062f53 --- /dev/null +++ b/src/test/java/com/seoultech/synergybe/domain/post/PostServiceTest.java @@ -0,0 +1,50 @@ +package com.seoultech.synergybe.domain.post; + +import com.seoultech.synergybe.domain.common.CustomPasswordEncoder; +import com.seoultech.synergybe.domain.post.repository.PostRepository; +import com.seoultech.synergybe.domain.post.service.PostService; +import com.seoultech.synergybe.domain.user.User; +import com.seoultech.synergybe.domain.user.repository.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.IntStream; +import static org.mockito.Mockito.*; +@SpringBootTest +@Transactional +class PostServiceTest { + + @Autowired + private PostService postService; + + @MockBean + private PostRepository postRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CustomPasswordEncoder passwordEncoder; + + @DisplayName("캐싱 함수를 통해 호출시 캐싱된 결과가 반환되어 호출을 1회만 한다.") + @Test + void getWeekBestPostList() { + // given + User user = User.builder().id(1000L).userToken("token").passwordEncoder(passwordEncoder).email("as@gmail.com").password("password").name("name").major("major").build(); + userRepository.save(user); + when(postRepository.findAllByLikeAndDate()).thenReturn(List.of(Post.builder().id(1L).title("title").content("content").user(user).build())); + + // when + IntStream.range(0, 10) + .forEach(i -> postService.getWeekBestPostList()); + + // then + verify(postRepository, times(1)).findAllByLikeAndDate(); + } + +}