diff --git a/src/main/java/com/server/capple/domain/board/controller/BoardController.java b/src/main/java/com/server/capple/domain/board/controller/BoardController.java index 70901b48..82035305 100644 --- a/src/main/java/com/server/capple/domain/board/controller/BoardController.java +++ b/src/main/java/com/server/capple/domain/board/controller/BoardController.java @@ -1,6 +1,7 @@ package com.server.capple.domain.board.controller; import com.server.capple.config.security.AuthMember; +import com.server.capple.domain.answer.dto.AnswerResponse; import com.server.capple.domain.board.dto.BoardRequest; import com.server.capple.domain.board.dto.BoardResponse; import com.server.capple.domain.board.entity.BoardType; @@ -75,4 +76,11 @@ private BaseResponse searchBoardsByKeyword( return BaseResponse.onSuccess(boardService.searchBoardsByKeyword(keyword)); } + @Operation(summary = "게시글 좋아요/취소 API", description = " 게시글 좋아요/취소 API 입니다." + + "pathvariable 으로 boardId를 주세요.") + @PostMapping("/{boardId}/heart") + public BaseResponse toggleBoardHeart(@AuthMember Member member, @PathVariable(value = "boardId") Long boardId) { + return BaseResponse.onSuccess(boardService.toggleBoardHeart(member, boardId)); + } + } diff --git a/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java b/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java index 1e659b37..5627f505 100644 --- a/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java +++ b/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java @@ -33,6 +33,7 @@ public static class BoardsGetByBoardType { @AllArgsConstructor @NoArgsConstructor public static class BoardsGetByBoardTypeBoardInfo { + private Long boardId; private Long writerId; private String content; private Integer heartCount; @@ -61,10 +62,20 @@ public static class BoardsSearchByKeyword { @AllArgsConstructor @NoArgsConstructor public static class BoardsSearchByKeywordBoardInfo { + private Long boardId; private Long writerId; private String content; private Integer heartCount; private Integer commentCount; private LocalDateTime createAt; } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class BoardToggleHeart { + private Long boardId; + private Boolean isLiked; + } } diff --git a/src/main/java/com/server/capple/domain/board/entity/Board.java b/src/main/java/com/server/capple/domain/board/entity/Board.java index 0ee20372..af811828 100644 --- a/src/main/java/com/server/capple/domain/board/entity/Board.java +++ b/src/main/java/com/server/capple/domain/board/entity/Board.java @@ -26,13 +26,9 @@ public class Board extends BaseEntity { @Column(nullable = false) private BoardType boardType; - @Column(nullable = false) private String content; - @Column(nullable = false) - private Integer heartCount; - @Column(nullable = false) private Integer commentCount; } diff --git a/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java b/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java index 1e44629d..337ffe5a 100644 --- a/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java +++ b/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java @@ -1,6 +1,5 @@ package com.server.capple.domain.board.mapper; -import com.server.capple.domain.board.dto.BoardRequest; import com.server.capple.domain.board.dto.BoardResponse; import com.server.capple.domain.board.entity.Board; import com.server.capple.domain.board.entity.BoardType; @@ -23,7 +22,6 @@ public Board toBoard( .writer(member) .boardType(boardType) .content(content) - .heartCount(heartCount) .commentCount(commentCount) .build(); } @@ -45,13 +43,15 @@ public BoardResponse.BoardsGetByBoardType toBoardsGetByBoardType( } public BoardResponse.BoardsGetByBoardTypeBoardInfo toBoardsGetByBoardTypeBoardInfo( - Board board - ) { + Board board, + Integer boardHeartsCount) { return BoardResponse.BoardsGetByBoardTypeBoardInfo.builder() + .boardId(board.getId()) .writerId(board.getWriter().getId()) .content(board.getContent()) - .heartCount(board.getHeartCount()) - .commentCount(board.getCommentCount()) + .heartCount(boardHeartsCount) + // TODO : 댓글 작성 API 나오면 추후 구현 + .commentCount(0) .createAt(board.getCreatedAt()) .build(); } @@ -63,12 +63,14 @@ public BoardResponse.BoardDelete toBoardDelete(Board board) { } public BoardResponse.BoardsSearchByKeywordBoardInfo toBoardsSearchByKeywordBoardInfo( - Board board + Board board, + Integer heartCount ) { return BoardResponse.BoardsSearchByKeywordBoardInfo.builder() + .boardId(board.getId()) .writerId(board.getWriter().getId()) .content(board.getContent()) - .heartCount(board.getHeartCount()) + .heartCount(heartCount) .commentCount(board.getCommentCount()) .createAt(board.getCreatedAt()) .build(); diff --git a/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java b/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java new file mode 100644 index 00000000..1da32f2d --- /dev/null +++ b/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java @@ -0,0 +1,60 @@ +package com.server.capple.domain.board.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.SetOperations; +import org.springframework.stereotype.Repository; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + +@Repository +@RequiredArgsConstructor +public class BoardHeartRedisRepository implements Serializable { + public static final String BOARD_HEART_KEY_PREFIX = "boardHeart-"; + public static final String MEMBER_KEY_PREFIX = "member-"; + + private final RedisTemplate redisTemplate; + + // 게시판 좋아요 토글 + public Boolean toggleBoardHeart(Long memberId, Long boardId) { + String key = BOARD_HEART_KEY_PREFIX + boardId.toString(); + String member = MEMBER_KEY_PREFIX + memberId.toString(); + SetOperations setOperations = redisTemplate.opsForSet(); + + //해당 key에 member가 존재하지 않으면 추가, 존재하면 삭제 + if (FALSE.equals(setOperations.isMember(key, member))) { + setOperations.add(key, member); + return TRUE; + } else { + setOperations.remove(key, member); + return FALSE; + } + } + + // 게시판 좋아요 수 조회 + public Integer getBoardHeartsCount(Long boardId) { + String key = BOARD_HEART_KEY_PREFIX + boardId.toString(); + Set members = redisTemplate.opsForSet().members(key); + return members != null ? members.size() : 0; + } + + // 좋아요 누른 게시판 조회 + public Set getMemberHeartsBoard(Long memberId) { + String member = MEMBER_KEY_PREFIX + memberId.toString(); + Set keys = redisTemplate.keys(BOARD_HEART_KEY_PREFIX + "*"); // 모든 키 조회 + Set boardIds = new HashSet<>(); + + for (String key : keys) { + if (redisTemplate.opsForSet().isMember(key, member)) { + String boardId = key.substring(BOARD_HEART_KEY_PREFIX.length()); + boardIds.add(Long.parseLong(boardId)); + } + } + return boardIds; + } +} \ No newline at end of file diff --git a/src/main/java/com/server/capple/domain/board/service/BoardService.java b/src/main/java/com/server/capple/domain/board/service/BoardService.java index 1a213b0a..79e19f68 100644 --- a/src/main/java/com/server/capple/domain/board/service/BoardService.java +++ b/src/main/java/com/server/capple/domain/board/service/BoardService.java @@ -13,4 +13,6 @@ public interface BoardService { BoardResponse.BoardDelete deleteBoard(Member member, Long boardId); BoardResponse.BoardsSearchByKeyword searchBoardsByKeyword(String keyword); + + BoardResponse.BoardToggleHeart toggleBoardHeart(Member member, Long boardId); } diff --git a/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java b/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java index 6caf7c13..c938b458 100644 --- a/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java +++ b/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java @@ -4,18 +4,17 @@ import com.server.capple.domain.board.entity.Board; import com.server.capple.domain.board.entity.BoardType; import com.server.capple.domain.board.mapper.BoardMapper; +import com.server.capple.domain.board.repository.BoardHeartRedisRepository; import com.server.capple.domain.board.repository.BoardRepository; import com.server.capple.domain.member.entity.Member; 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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; -import java.util.Optional; @Service @RequiredArgsConstructor @@ -23,6 +22,7 @@ public class BoardServiceImpl implements BoardService { private final BoardRepository boardRepository; + private final BoardHeartRedisRepository boardHeartRedisRepository; private final BoardMapper boardMapper; @Override @@ -49,7 +49,7 @@ public BoardResponse.BoardsGetByBoardType getBoardsByBoardType(BoardType boardTy throw new RestApiException(BoardErrorCode.BOARD_BAD_REQUEST); } return boardMapper.toBoardsGetByBoardType(boards.stream() - .map(boardMapper::toBoardsGetByBoardTypeBoardInfo) + .map(board -> boardMapper.toBoardsGetByBoardTypeBoardInfo(board, boardHeartRedisRepository.getBoardHeartsCount(board.getId()))) .toList() ); } @@ -71,9 +71,18 @@ public BoardResponse.BoardDelete deleteBoard(Member member, Long boardId) { public BoardResponse.BoardsSearchByKeyword searchBoardsByKeyword(String keyword) { List boards = boardRepository.findBoardsByKeyword(keyword); return boardMapper.toBoardsSearchByKeyword(boards.stream() - .map(boardMapper::toBoardsSearchByKeywordBoardInfo) + .map(board -> boardMapper.toBoardsSearchByKeywordBoardInfo(board, boardHeartRedisRepository.getBoardHeartsCount(board.getId()))) .toList()); } + @Override + public BoardResponse.BoardToggleHeart toggleBoardHeart(Member member, Long boardId) { + Board board = boardRepository.findById(boardId) + .orElseThrow(() -> new RestApiException(BoardErrorCode.BOARD_NOT_FOUND)); + + Boolean isLiked = boardHeartRedisRepository.toggleBoardHeart(member.getId(), board.getId()); + return new BoardResponse.BoardToggleHeart(boardId, isLiked); + } + }