Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 무한스크롤의 중복 컨텐츠 문제 해결 #192

Merged
merged 7 commits into from
Oct 1, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

@Tag(name = "답변 API", description = "답변 API입니다.")
@RestController
@RequiredArgsConstructor
Expand All @@ -41,11 +43,13 @@ public BaseResponse<SliceResponse<AnswerInfo>> getAnswerList(
@AuthMember Member member,
@Parameter(description = "질문 식별자")
@PathVariable(value = "questionId") Long questionId,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "답변 수정 API", description = " 답변 수정 API 입니다." +
Expand Down Expand Up @@ -74,21 +78,25 @@ public BaseResponse<AnswerResponse.AnswerLike> toggleAnswerHeart(@AuthMember Mem
@GetMapping
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberAnswer(
@AuthMember Member member,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberAnswer(member, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getMemberAnswer(member, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "좋아한 답변 조회 API", description = " 좋아한 답변 조회 API 입니다.")
@GetMapping("/heart")
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberHeartAnswer(
@AuthMember Member member,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Set;

public interface AnswerRepository extends JpaRepository<Answer, Long> {
Optional<Answer> findById(Long answerId);

Slice<Answer> findByIdIn(Set<Long> answerIds, Pageable pageable);
Slice<Answer> findByIdInAndCreatedAtBefore(Set<Long> answerIds, LocalDateTime thresholdDate, Pageable pageable);

boolean existsByQuestionAndMember(Question question, Member member);

@Query("SELECT a AS answer, (r IS NOT NULL) AS isReported " +
"FROM Answer a " +
"LEFT JOIN " +
"Report r ON r.answer = a " +
"WHERE a.question.id = :questionId")
"WHERE a.createdAt <= :thresholdDate AND a.question.id = :questionId")
Slice<AnswerInfoInterface> findByQuestion(
@Param("questionId") Long questionId,
LocalDateTime thresholdDate,
Pageable pageable);

@Query("SELECT a FROM Answer a WHERE a.member = :member and a.deletedAt is null")
Slice<Answer> findByMember(@Param("member") Member member, Pageable pageable);
Slice<Answer> findByMemberAndCreatedAtBefore(@Param("member") Member member, LocalDateTime thresholdDate, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.server.capple.global.common.SliceResponse;
import org.springframework.data.domain.Pageable;

import java.time.LocalDateTime;


public interface AnswerService {
AnswerResponse.AnswerId createAnswer(Member member, Long questionId, AnswerRequest request);
Expand All @@ -21,9 +23,9 @@ public interface AnswerService {

AnswerResponse.AnswerLike toggleAnswerHeart(Member loginMember, Long answerId);

SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Pageable pageable);
SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, LocalDateTime thresholdDate, Pageable pageable);

SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Pageable pageable);
SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, LocalDateTime thresholdDate, Pageable pageable);

SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, Pageable pageable);
SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, LocalDateTime thresholdDate, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand Down Expand Up @@ -86,14 +88,15 @@ public AnswerResponse.AnswerId deleteAnswer(Member loginMember, Long answerId) {
@Override
public AnswerLike toggleAnswerHeart(Member loginMember, Long answerId) {
Member member = memberService.findMember(loginMember.getId());

answerRepository.findById(answerId).orElseThrow(() -> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND));
Boolean isLiked = answerHeartRedisRepository.toggleAnswerHeart(member.getId(), answerId);
return new AnswerLike(answerId, isLiked);
}

@Override
public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Pageable pageable) {
Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, pageable);
public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = (thresholdDate == null) ? LocalDateTime.now() : thresholdDate;
Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, thresholdDate, pageable);
return SliceResponse.toSliceResponse(answerInfoSliceInterface, answerInfoSliceInterface.getContent().stream().map(
answerInfoDto -> answerMapper.toAnswerInfo(
answerInfoDto.getAnswer(),
Expand All @@ -107,8 +110,9 @@ public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, P

// 유저가 작성한 답변 조회
@Override
public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Pageable pageable) {
Slice<Answer> answerSlice = answerRepository.findByMember(member, pageable);
public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = (thresholdDate == null) ? LocalDateTime.now() : thresholdDate;
Slice<Answer> answerSlice = answerRepository.findByMemberAndCreatedAtBefore(member, thresholdDate, pageable);
return SliceResponse.toSliceResponse(
answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
Expand All @@ -121,8 +125,9 @@ public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Pageable p

// 유저가 좋아한 답변 조회
@Override
public SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, Pageable pageable) {
Slice<Answer> answerSlice = answerRepository.findByIdIn(answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()), pageable);
public SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = (thresholdDate == null) ? LocalDateTime.now() : thresholdDate;
Slice<Answer> answerSlice = answerRepository.findByIdInAndCreatedAtBefore(answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()), thresholdDate, pageable);
return SliceResponse.toSliceResponse(answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
answer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.server.capple.global.common.BaseResponse;
import com.server.capple.global.common.SliceResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -20,6 +21,8 @@
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

@Tag(name = "게시판 API", description = "게시판 관련 API")
@RestController
@RequiredArgsConstructor
Expand All @@ -46,9 +49,11 @@ private BaseResponse<BoardId> createBoard(@AuthMember Member member,
private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardTypeWithRedis(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber, @RequestParam(defaultValue = "1000", required = false) Integer pageSize
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardTypeWithRedis(member, boardType, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.getBoardsByBoardTypeWithRedis(member, boardType, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "카테고리별 게시글 조회", description = "카테고리별 게시글을 조회합니다.")
Expand All @@ -59,9 +64,11 @@ private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardTypeWithRedis(
private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardType(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber, @RequestParam(defaultValue = "1000", required = false) Integer pageSize
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "게시글 검색 API", description = "게시글을 검색합니다. 자유게시판에서만 검색이 가능합니다.")
Expand All @@ -71,9 +78,11 @@ private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardType(
@GetMapping("/search")
private BaseResponse<SliceResponse<BoardInfo>> searchBoardsByKeyword(
@AuthMember Member member, @RequestParam(name = "keyword") String keyword,
@Parameter(description = "조회 기준 시각")
@RequestParam(required = false) LocalDateTime thresholdDate,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(member, keyword, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(member, keyword, thresholdDate, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.time.LocalDateTime;

public interface BoardRepository extends JpaRepository<Board, Long> {

Expand All @@ -18,23 +18,23 @@ public interface BoardRepository extends JpaRepository<Board, Long> {
"(CASE WHEN b.writer = :member THEN TRUE ELSE FALSE END) AS isMine " +
"FROM Board b " +
"LEFT JOIN BoardHeart bh ON b = bh.board AND bh.member = :member " +
"WHERE :boardType IS NULL OR b.boardType = :boardType")
Slice<BoardInfoInterface> findBoardInfosByMemberAndBoardType(Member member, BoardType boardType, Pageable pageable);
"WHERE b.createdAt <= :thresholdDate AND (:boardType IS NULL OR b.boardType = :boardType)")
Slice<BoardInfoInterface> findBoardInfosByMemberAndBoardTypeAndCreatedAtBefore(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable);

@Query("SELECT DISTINCT b AS board, " +
"(CASE WHEN bh.isLiked = TRUE THEN TRUE ELSE FALSE END) AS isLike, " +
"(CASE WHEN b.writer = :member THEN TRUE ELSE FALSE END) AS isMine " +
"FROM Board b " +
"LEFT JOIN BoardHeart bh ON b = bh.board AND bh.member = :member " +
"WHERE b.content LIKE %:keyword% AND b.boardType = 0 ORDER BY b.createdAt DESC") //FREETYPE = 0
Slice<BoardInfoInterface> findBoardInfosByMemberAndKeyword(Member member, String keyword, Pageable pageable);
"WHERE b.createdAt <= :thresholdDate AND b.content LIKE %:keyword% AND b.boardType = 0") //FREETYPE = 0
Slice<BoardInfoInterface> findBoardInfosByMemberAndKeywordAndCreatedAtBefore(Member member, String keyword, LocalDateTime thresholdDate, Pageable pageable);


//redis 성능 테스트용
@Query("SELECT DISTINCT b AS board, " +
"(CASE WHEN b.writer = :member THEN TRUE ELSE FALSE END) AS isMine " +
"FROM Board b " +
"WHERE :boardType IS NULL OR b.boardType = :boardType ORDER BY b.createdAt DESC")
Slice<BoardInfoInterface> findBoardInfosForRedis(Member member, BoardType boardType, Pageable pageable);
"WHERE b.createdAt <= :thresholdDate AND (:boardType IS NULL OR b.boardType = :boardType)")
Slice<BoardInfoInterface> findBoardInfosForRedisAndCreatedAtBefore(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
import com.server.capple.global.common.SliceResponse;
import org.springframework.data.domain.Pageable;

import java.time.LocalDateTime;


public interface BoardService {
BoardId createBoard(Member member, BoardType boardType, String content);

SliceResponse<BoardInfo> getBoardsByBoardTypeWithRedis(Member member, BoardType boardType, Pageable pageable);
SliceResponse<BoardInfo> getBoardsByBoardTypeWithRedis(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable);

SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType boardType, Pageable pageable);
SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable);

SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, Pageable pageable);
SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, LocalDateTime thresholdDate, Pageable pageable);

BoardId deleteBoard(Member member, Long boardId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

import static com.server.capple.domain.board.dto.BoardResponse.BoardInfo;

@Service
Expand All @@ -48,8 +50,9 @@ public BoardId createBoard(Member member, BoardType boardType, String content) {
}

@Override
public SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType boardType, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndBoardType(member, boardType, pageable);
public SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = thresholdDate == null ? LocalDateTime.now() : thresholdDate;
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndBoardTypeAndCreatedAtBefore(member, boardType, thresholdDate, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo ->
boardMapper.toBoardInfo(
Expand All @@ -61,8 +64,9 @@ public SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType bo
}

@Override
public SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndKeyword(member, keyword, pageable);
public SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = thresholdDate == null ? LocalDateTime.now() : thresholdDate;
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndKeywordAndCreatedAtBefore(member, keyword, thresholdDate, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo ->
boardMapper.toBoardInfo(
Expand All @@ -77,8 +81,9 @@ public SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyw
redis 성능 테스트 용
*/
@Override
public SliceResponse<BoardInfo> getBoardsByBoardTypeWithRedis(Member member, BoardType boardType, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosForRedis(member, boardType, pageable);
public SliceResponse<BoardInfo> getBoardsByBoardTypeWithRedis(Member member, BoardType boardType, LocalDateTime thresholdDate, Pageable pageable) {
thresholdDate = thresholdDate == null ? LocalDateTime.now() : thresholdDate;
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosForRedisAndCreatedAtBefore(member, boardType, thresholdDate, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo -> {
int heartCount = boardHeartRedisRepository.getBoardHeartsCount(sliceBoardInfo.getBoard().getId());
Expand Down
Loading
Loading