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] 게시글, 댓글 목록 페이지네이션 구현 #160

Merged
merged 8 commits into from
Sep 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
import com.server.capple.config.security.AuthMember;
import com.server.capple.domain.board.dto.BoardRequest;
import com.server.capple.domain.board.dto.BoardResponse.BoardId;
import com.server.capple.domain.board.dto.BoardResponse.BoardsGetBoardInfos;
import com.server.capple.domain.board.dto.BoardResponse.BoardInfo;
import com.server.capple.domain.board.dto.BoardResponse.ToggleBoardHeart;
import com.server.capple.domain.board.entity.BoardType;
import com.server.capple.domain.board.service.BoardService;
import com.server.capple.domain.member.entity.Member;
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.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

@Tag(name = "게시판 API", description = "게시판 관련 API")
Expand All @@ -40,37 +43,37 @@ private BaseResponse<BoardId> createBoard(@AuthMember Member member,
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping("/redis")
private BaseResponse<BoardsGetBoardInfos> getBoardsByBoardTypeWithRedis(
private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardTypeWithRedis(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType
// TODO: 페이징 프론트 이슈로 추후 구현
// @PageableDefault(sort = "created_at", direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardTypeWithRedis(member, boardType));
@RequestParam(name = "boardType", required = false) BoardType boardType,
@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"))));
}

@Operation(summary = "카테고리별 게시글 조회", description = "카테고리별 게시글을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping()
private BaseResponse<BoardsGetBoardInfos> getBoardsByBoardType(
private BaseResponse<SliceResponse<BoardInfo>> getBoardsByBoardType(
@AuthMember Member member,
@RequestParam(name = "boardType", required = false) BoardType boardType
// TODO: 페이징 프론트 이슈로 추후 구현
// @PageableDefault(sort = "created_at", direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable
@RequestParam(name = "boardType", required = false) BoardType boardType,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber, @RequestParam(defaultValue = "1000", required = false) Integer pageSize
) {
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType));
return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "게시글 검색 API", description = "게시글을 검색합니다. 자유게시판에서만 검색이 가능합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "COMMON200", description = "성공"),
})
@GetMapping("/search")
private BaseResponse<BoardsGetBoardInfos> searchBoardsByKeyword(@AuthMember Member member,
@RequestParam(name = "keyword") String keyword) {
return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(member, keyword));
private BaseResponse<SliceResponse<BoardInfo>> searchBoardsByKeyword(
@AuthMember Member member, @RequestParam(name = "keyword") String keyword,
@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"))));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import lombok.NoArgsConstructor;

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

public class BoardResponse {

Expand All @@ -15,20 +14,11 @@ public class BoardResponse {
public static class BoardId {
private Long boardId;
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class BoardsGetBoardInfos {
private List<BoardsGetBoardInfo> boards;
}

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class BoardsGetBoardInfo {
public static class BoardInfo {
private Long boardId;
private Long writerId;
private String content;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.server.capple.domain.board.mapper;

import com.server.capple.domain.board.dto.BoardResponse.BoardsGetBoardInfo;
import com.server.capple.domain.board.dto.BoardResponse.BoardInfo;
import com.server.capple.domain.board.entity.Board;
import com.server.capple.domain.board.entity.BoardType;
import com.server.capple.domain.member.entity.Member;
Expand All @@ -21,8 +21,8 @@ public Board toBoard(Member member, BoardType boardType, String content) {
}

//redis
public BoardsGetBoardInfo toBoardsGetBoardInfo(Board board, Integer boardHeartsCount, Boolean isLiked, Boolean isMine) {
return BoardsGetBoardInfo.builder()
public BoardInfo toBoardInfo(Board board, Integer boardHeartsCount, Boolean isLiked, Boolean isMine) {
return BoardInfo.builder()
.boardId(board.getId())
.writerId(board.getWriter().getId())
.content(board.getContent())
Expand All @@ -36,8 +36,8 @@ public BoardsGetBoardInfo toBoardsGetBoardInfo(Board board, Integer boardHeartsC
}

//rdb
public BoardsGetBoardInfo toBoardsGetBoardInfo(Board board, Boolean isLiked, Boolean isMine) {
return BoardsGetBoardInfo.builder()
public BoardInfo toBoardInfo(Board board, Boolean isLiked, Boolean isMine) {
return BoardInfo.builder()
.boardId(board.getId())
.writerId(board.getWriter().getId())
.content(board.getContent())
Expand All @@ -50,3 +50,4 @@ public BoardsGetBoardInfo toBoardsGetBoardInfo(Board board, Boolean isLiked, Boo
.build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,37 @@
import com.server.capple.domain.board.entity.Board;
import com.server.capple.domain.board.entity.BoardType;
import com.server.capple.domain.member.entity.Member;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface BoardRepository extends JpaRepository<Board, Long> {

@Query("SELECT b AS board, " +
"(CASE WHEN bh.member = :member AND bh.isLiked = TRUE THEN TRUE ELSE FALSE END) AS isLike, " +
@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 " +
"WHERE :boardType IS NULL OR b.boardType = :boardType ORDER BY b.createdAt DESC")
List<BoardInfoInterface> findBoardInfosByMemberAndBoardType(Member member, BoardType boardType);
"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);

@Query("SELECT b AS board, " +
"(CASE WHEN bh.member = :member AND bh.isLiked = TRUE THEN TRUE ELSE FALSE END) AS isLike, " +
@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 " +
"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
List<BoardInfoInterface> findBoardInfosByMemberAndKeyword(Member member, String keyword);
Slice<BoardInfoInterface> findBoardInfosByMemberAndKeyword(Member member, String keyword, 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);

List<Board> findBoardsByBoardType(BoardType boardType);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package com.server.capple.domain.board.service;

import com.server.capple.domain.board.dto.BoardResponse.BoardId;
import com.server.capple.domain.board.dto.BoardResponse.BoardsGetBoardInfos;
import com.server.capple.domain.board.dto.BoardResponse.BoardInfo;
import com.server.capple.domain.board.dto.BoardResponse.ToggleBoardHeart;
import com.server.capple.domain.board.entity.Board;
import com.server.capple.domain.board.entity.BoardType;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.global.common.SliceResponse;
import org.springframework.data.domain.Pageable;


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

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

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

BoardId deleteBoard(Member member, Long boardId);
SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, Pageable pageable);

BoardsGetBoardInfos searchBoardsByKeyword(Member member, String keyword);
BoardId deleteBoard(Member member, Long boardId);

ToggleBoardHeart toggleBoardHeart(Member member, Long boardId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.server.capple.domain.board.dao.BoardInfoInterface;
import com.server.capple.domain.board.dto.BoardResponse.BoardId;
import com.server.capple.domain.board.dto.BoardResponse.BoardsGetBoardInfos;
import com.server.capple.domain.board.dto.BoardResponse.ToggleBoardHeart;
import com.server.capple.domain.board.entity.Board;
import com.server.capple.domain.board.entity.BoardHeart;
Expand All @@ -15,25 +14,28 @@
import com.server.capple.domain.boardSubscribeMember.service.BoardSubscribeMemberService;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.domain.notifiaction.service.NotificationService;
import com.server.capple.global.common.SliceResponse;
import com.server.capple.global.exception.RestApiException;
import com.server.capple.global.exception.errorCode.BoardErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BoardServiceImpl implements BoardService {

private final BoardRepository boardRepository;
private final BoardHeartRedisRepository boardHeartRedisRepository;
private final BoardMapper boardMapper;
private final BoardHeartRepository boardHeartRepository;
private final BoardHeartMapper boardHeartMapper;
private final NotificationService notificationService;
private final BoardHeartRedisRepository boardHeartRedisRepository;
private final BoardSubscribeMemberService boardSubscribeMemberService;

@Override
Expand All @@ -46,46 +48,48 @@ public BoardId createBoard(Member member, BoardType boardType, String content) {
}

@Override
public BoardsGetBoardInfos getBoardsByBoardType(Member member, BoardType boardType) {
List<BoardInfoInterface> boardInfos = boardRepository.findBoardInfosByMemberAndBoardType(member, boardType);

return new BoardsGetBoardInfos(boardInfos.stream()
.map(boardInfo -> boardMapper.toBoardsGetBoardInfo(boardInfo.getBoard(), boardInfo.getIsLike(), boardInfo.getIsMine()))
public SliceResponse<BoardInfo> getBoardsByBoardType(Member member, BoardType boardType, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndBoardType(member, boardType, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo ->
boardMapper.toBoardInfo(
sliceBoardInfo.getBoard(),
sliceBoardInfo.getIsLike(),
sliceBoardInfo.getIsMine()))
.toList()
);
}

/*
redis 성능 테스트 용
*/
@Override
public BoardsGetBoardInfos getBoardsByBoardTypeWithRedis(Member member, BoardType boardType) {
List<Board> boards;
if (boardType == null) {
boards = boardRepository.findAll();
} else {
boards = boardRepository.findBoardsByBoardType(boardType);
}

return new BoardsGetBoardInfos(boards.stream()
.map(board -> {
int heartCount = boardHeartRedisRepository.getBoardHeartsCount(board.getId());
boolean isLiked = boardHeartRedisRepository.isMemberLikedBoard(member.getId(), board.getId());
boolean isMine = board.getWriter().getId().equals(member.getId());
return boardMapper.toBoardsGetBoardInfo(board, heartCount, isLiked, isMine);
})
public SliceResponse<BoardInfo> searchBoardsByKeyword(Member member, String keyword, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosByMemberAndKeyword(member, keyword, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo ->
boardMapper.toBoardInfo(
sliceBoardInfo.getBoard(),
sliceBoardInfo.getIsLike(),
sliceBoardInfo.getIsMine()))
.toList()
);
}

/*
redis 성능 테스트 용
*/
@Override
public BoardsGetBoardInfos searchBoardsByKeyword(Member member, String keyword) {
List<BoardInfoInterface> boardInfos = boardRepository.findBoardInfosByMemberAndKeyword(member, keyword);

return new BoardsGetBoardInfos(boardInfos.stream()
.map(boardInfo -> boardMapper.toBoardsGetBoardInfo(boardInfo.getBoard(), boardInfo.getIsLike(), boardInfo.getIsMine()))
.toList()
);
public SliceResponse<BoardInfo> getBoardsByBoardTypeWithRedis(Member member, BoardType boardType, Pageable pageable) {
Slice<BoardInfoInterface> sliceBoardInfos = boardRepository.findBoardInfosForRedis(member, boardType, pageable);

return SliceResponse.toSliceResponse(sliceBoardInfos, sliceBoardInfos.getContent().stream().map(sliceBoardInfo -> {
int heartCount = boardHeartRedisRepository.getBoardHeartsCount(sliceBoardInfo.getBoard().getId());
boolean isLiked = boardHeartRedisRepository.isMemberLikedBoard(member.getId(), sliceBoardInfo.getBoard().getId());
return boardMapper.toBoardInfo(
sliceBoardInfo.getBoard(),
heartCount,
isLiked,
sliceBoardInfo.getIsMine());
})
.toList());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@

import com.server.capple.config.security.AuthMember;
import com.server.capple.domain.boardComment.dto.BoardCommentRequest;
import com.server.capple.domain.boardComment.dto.BoardCommentResponse.ToggleBoardCommentHeart;
import com.server.capple.domain.boardComment.dto.BoardCommentResponse.BoardCommentId;
import com.server.capple.domain.boardComment.dto.BoardCommentResponse.BoardCommentInfos;
import com.server.capple.domain.boardComment.dto.BoardCommentResponse.BoardCommentInfo;
import com.server.capple.domain.boardComment.dto.BoardCommentResponse.ToggleBoardCommentHeart;
import com.server.capple.domain.boardComment.service.BoardCommentService;
import com.server.capple.domain.member.entity.Member;
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.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

@Tag(name = "게시글 댓글 API", description = "게시글 댓글 API입니다.")
Expand Down Expand Up @@ -52,8 +55,10 @@ public BaseResponse<ToggleBoardCommentHeart> heartBoardComment(@AuthMember Membe

@Operation(summary = "게시글 댓글 리스트 조회 API", description = " 게시글 댓글 리스트 조회 API 입니다. pathVariable 으로 boardId를 주세요.")
@GetMapping("/{boardId}")
public BaseResponse<BoardCommentInfos> getBoardCommentInfos(@AuthMember Member member, @PathVariable(value = "boardId") Long boardId) {
return BaseResponse.onSuccess(boardCommentService.getBoardCommentInfos(member,boardId));
public BaseResponse<SliceResponse<BoardCommentInfo>> getBoardCommentInfos(@AuthMember Member member, @PathVariable(value = "boardId") Long boardId,
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(boardCommentService.getBoardCommentInfos(member,boardId, PageRequest.of(pageNumber,pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,4 @@ public static class BoardCommentInfo {
private Boolean isReport;
private LocalDateTime createdAt;
}

@Getter
@AllArgsConstructor
public static class BoardCommentInfos {
private List<BoardCommentInfo> boardCommentInfos;
}
}
Loading
Loading