티스토리 뷰

Project 댕린이집

[게시판] 좋아요

xoo | 수진 2024. 6. 28. 22:29

좋아요 기능 구현한 것을 기록해보겠습니다!

먼저, 게시글 좋아요 기능을 구현하기 위해서 진행단계를 생각해봤습니다.

1. 좋아요 버튼 추가
2. 좋아요 상태 관리 -> 한 번 누르면 '좋아요'
                                -> 한 번 또 누르면 '좋아요 취소'
3. 좋아요 API 호출

 


 

 [ 클라이언트 ] 

BoardView.js

 

먼저 좋아요 여부의 상태를 관리하기 위해 useState훅을 사용해줍니다.

let [isLiked, setIsLiked] = useState(false);  // 좋아요 여부 상태

 

그리고 리턴문 안에 좋아요 수를 표시해주는 부분을 작성해줍니다.

                            <div className="board-view-row">
                                <label>🖤좋아요</label>
                                <label>{board.user_like}</label>
                            </div>

 

리턴문 안에 좋아요 버튼도 추가해줬습니다.

onClick으로 버튼을 누르면 like라는 함수가 실행되게 해주고

삼항연산자를 통해 처음 눌렀을 시, 빨간 하트(좋아요)

다시 눌렀을 시, 하얀 하트(좋아요 취소)로 렌더링 되게끔 해줬습니다. 

    <div className="like-button-container">
                                <button 
                                    className={`like-button ${isLiked ? 'liked' : ''}`} 
                                    onClick={like}
                                >
                                    {isLiked ? '💘' : '🤍'}
                                </button>
                            </div>

 

이제 좋아요 버튼을 눌렀을 때 실행되는 like 함수를 작성해야겠죠.

  • 서버에 post 요청을 보내는데 헤더에 액세스토큰을 같이 보내줍니다.
  • URL에서 ${board.id}는 board 객체의 id 속성을 의미합니다.
  • 서버로부터의 응답을 data 변수에 저장하고
  • 응답 코드가 200 이라면 좋아요 상태를 반전시킵니다. (true는 false로, false는 true로)
  • 현재 게시글의 좋아요 수를 업데이트 해주어야 합니다
    • ...prevBoard : 이전의 상태를 복사하고
    • user_like: prevBoard.user_like + (isLiked ? -1 : 1) : user_like 속성을 업데이트합니다. isLiked가 true이면 -1, false이면 +1
    • 여기서 조금 헷갈릴 수 있는게 좋아요를 하면 -1이고, 좋아요를 안하면 +1 이라는 뜻이 아니라
    • isLiked가 true인 경우: 현재 사용자가 이미 좋아요를 눌렀다는 의미입니다. -> 한번 더 누르면 좋아요 취소 -> -1
    • isLiked가 false인 경우: 현재 사용자가 아직 좋아요를 누르지 않았다는 의미입니다. -> 누르면 좋아요 -> +1
    // 좋아요
    let like = async () => {
        try {
            let res = await axios.post(`http://localhost:8082/api/v1/auth/y/userLike/${board.id}`, {}, {
                headers: {
                    'Authorization': 'Bearer ' + cookies.accessToken,
                }
                
            });

            let { data } = res;

            if(data.code === "200") {
                setIsLiked(!isLiked);  // 좋아요 상태 반전
                setBoard(prevBoard => ({
                    ...prevBoard,
                    user_like: prevBoard.user_like + (isLiked ? -1 : 1)
                }));
            } else {
                alert(data.message);
                console.log("Like failed. Message:", data.message);
            }

        } catch (error) {
            console.error("Error: ", error);
        }

    };

 

마지막으로 로그인이 되어 있을 때 게시글 상세 정보를 불러오는 API를 호출하는 함수에 좋아요 상태를 갱신하는 코드도 꼭 넣어주어야 합니다!

setIsLiked(data.isLiked);  // 좋아요 상태 갱신

 


 

[ 서버 ] 

LikeListEntity.java

pk값인 id를 가지고있고

board와 user와는 다대일 관계를 가지고 있습니다.

package com.example.demo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "likelist")
@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
public class LikeListEntity {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	// BoardEntity와 다대일 관계
	@ManyToOne
	@JoinColumn(name = "board_id", insertable = false, updatable = false)
	private BoardEntity board;
	
	// UserEntity와 다대일 관계 
	@ManyToOne
	@JoinColumn(name = "user_id", insertable = false, updatable = false)
	private UserEntity user;
	
}

 

LikeListDTO.java

package com.example.demo.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LikeListDTO {
	
	private Long id;
	
	@JsonProperty("user_id")
	private Long userId;

}

 

LikeListRepository.java

특정 사용자와 게시물 간의 좋아요 관계를 찾기 위한 리포지토리 입니다.

Optional<LikeListEntity>를 반환하여, 좋아요 관계가 존재하지 않을 경우에도 안전하게 처리할 수 있습니다.

package com.example.demo.repository;

import com.example.demo.entity.BoardEntity;
import com.example.demo.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.LikeListEntity;

import java.util.Optional;

@Repository
public interface LikeListRepository extends JpaRepository<LikeListEntity, Long> {

    Optional<LikeListEntity> findByUserAndBoard(UserEntity user, BoardEntity board);

}

 

LikeListController.java

컨트롤러 입니다.

  • LikeListService 를 주입받습니다.
  • URL 경로에 {boardId} 경로 변수를 포함한 POST요청을 처리해줍니다.
  • insert메서드는 boardId와 Authentication 객체를 매개변수로 받는데 
  • @PathVariable은 경로 변수로 전달된 boardId 값을 받아옵니다.
  • Authentication 객체는 현재 인증된 사용자의 정보를 포함합니다.
  • 인증된 사용자의 이름(아이디)을 userId 변수에 저장합니다.
  • LikeListService의 insert 메서드를 호출하여 boardId와 userId를 통해 좋아요 작업을 수행하고, 결과를 ResDTO 객체로 반환받습니다.
  • 좋아요 작업의 결과를 클라이언트에게 반환합니다.
package com.example.demo.controller;

import com.example.demo.dto.ResDTO;
import com.example.demo.service.LikeListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/auth/y")
public class LikeListController {

    @Autowired
    private LikeListService likeListService;

    /**
     기능 : 좋아요
     url : /userLike
     request data :
     response data : 좋아요 성공
     */
    @PostMapping("/userLike/{boardId}")
    public ResDTO insert(@PathVariable("boardId") Long boardId, Authentication authentication) {
        String userId = authentication.getName(); // 인증된 사용자의 이름을 userId로 사용
        ResDTO resDTO = likeListService.insert(boardId, userId);
        return resDTO;
    }
}

 

 

LikeListService.java

insertLike 메서드

사용자가 특정 게시물에 대해 좋아요를 추가하거나 취소하는 기능

  • 먼저, 필요한 레포지토리들을 주입받습니다.
  • userId로 사용자 정보를 조회하고
  • boardId로 게시물 정보를 조회합니다.
  • 이미 좋아요를 눌렀는지 안눌렀는지 여부를 판별해야겠죠? 
    likeListRepository의 findByUserAndBoard 를 사용하여 특정 boardId와 userId에 맞는 게시물이 .isPresent() 존재하는지 확인해줍니다.
  • 이미 좋아요를 눌렀고, 좋아요 수가 0보다 크다면 (음수 방지)
    • likeListRepository의 delete를 사용하여 좋아요를 삭제해주고
    • 좋아요 수를 -1 감소시켜줍니다.
  • 좋아요가 되어있지 않다면
    • LikeListEntity에 해당 user와 board를 저장해주고
    • likeListRepository의 save를 사용하여 업데이트 된 LikeListEntity를 저장해줍니다.
    • 좋아요 수도 +1 증가시켜줍니다.
  • boardRepository의 save를 사용하여 게시물 정보(board)도 저장해줍니다.

getLikeInfo 메서드

특정 게시물에 대해 현재 사용자가 해당 게시물을 좋아요했는지 여부와 총 좋아요 수를 조회하는 기능

 

package com.example.demo.service;

import com.example.demo.dto.ResDTO;
import com.example.demo.entity.BoardEntity;
import com.example.demo.entity.LikeListEntity;
import com.example.demo.entity.UserEntity;
import com.example.demo.repository.BoardRepository;
import com.example.demo.repository.LikeListRepository;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;


@Service
public class LikeListService {
	
	@Autowired
	private LikeListRepository likeListRepository;

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private BoardRepository boardRepository;

	// LikeService에서만 좋아요 기능을 처리
	// LikeListService의 insertLike 메서드에서 boardEntity의 userLike 필드를 업데이트 (좋아요 수)
	@Transactional
	public ResDTO insertLike(Long boardId, String userId) {

		UserEntity user = userRepository.findByUserId(userId);
		BoardEntity board = boardRepository.findById(boardId).orElse(null);

		if (user == null || board == null) {
			return ResDTO.builder()
					.code("404")
					.message("유저 또는 게시물을 찾을 수 없습니다.")
					.build();
		}

		Optional<LikeListEntity> existingLike = likeListRepository.findByUserAndBoard(user, board);

		// 이미 좋아요 했다면? && 좋아요 수가 0보다 크다면? (음수 되는 것 방지)
		if (existingLike.isPresent() && board.getUserLike() > 0) {
			// 좋아요 삭제
			//	likeListRepository.deleteByUserAndBoard(user, board);
			likeListRepository.delete(existingLike.get());
			board.setUserLike(board.getUserLike() - 1);  // 좋아요 수 감소
		} else {
		// 좋아요 안되어있다면?
			// 좋아요 추가
			LikeListEntity likeListEntity = LikeListEntity.builder()
					.board(board)
					.user(user)
					.build();
			likeListRepository.save(likeListEntity);  // 추가한 좋아요를 저장
			board.setUserLike(board.getUserLike() + 1); // 좋아요 수 증가
		}

		boardRepository.save(board);  // 업데이트된 좋아요 수 저장

		return ResDTO.builder()
					.code("200")
					.message(existingLike.isPresent() ? "좋아요 취소 성공" : "좋아요 성공")
					.build();
	}

	// 특정 게시글에 대해 현재 사용자가 해당 게시글을 좋아요했는지 여부와 총 좋아요 수를 조회하는 메서드
		public Map<String, Object> getLikeInfo(Long boardId, String userId) {
			// 사용자 조회
			UserEntity user = userRepository.findByUserId(userId);
			// 게시글 조회
			BoardEntity board = boardRepository.findById(boardId)
					.orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다."));

			// 사용자가 해당 게시글을 좋아요했는지 여부 확인
			// 좋아요가 존재하면 true, 존재하지 않으면 false
			boolean isLiked = likeListRepository.findByUserAndBoard(user, board).isPresent();

			// board entity에서 직접 저장된 좋아요 수를 가져옴
			int likeCount = board.getUserLike();

			// 좋아요 여부(boolean)와 좋아요 수(int)를 함께 저장하기 위해 HashMap을 사용
			// 결과를 저장할 맵 생성
			Map<String, Object> likeInfo = new HashMap<>();
			likeInfo.put("isLiked", isLiked);
			likeInfo.put("likeCount", likeCount);

			return likeInfo;
		}

	}

 

 


 

 

💣 [ 트러블 슈팅 ]  좋아요 한 user_id와 board_id가 DB에 저장되지 않는 이슈 

문제

DB에 들어가 확인해보면 이렇게 likelist의 id값은 저장이 되지만 좋아요를 한 user_id와 board_id가 insert 되지 않고 있습니다.

 

원인

실행했을때 user_id와 board_id가 잘 들어오는지 디버깅을 해본 결과 값은 서버에 잘 들어오고 있었습니다. 그렇다면 DB에 저장되지 않는 것이 문제인데…간단한 문제였습니다.

LikeListEntity에서

insertable = false, updatable = false 속성이 설정되어 있으면 해당 필드에 대해 JPA가 insert 또는 update SQL 문을 생성할 때 이 필드를 포함하지 않습니다. 이는 데이터베이스에 값을 삽입하거나 갱신할 때 해당 필드가 무시된다는 것을 의미합니다.

 

해결

insetable, updatable 속성을 삭제해주었습니다.

 

결과

user_id와 board_id가 잘 저장됩니다 👍

 

 


 

 

💣 [ 트러블 슈팅 ]  좋아요 여부와 횟수가 리셋되는 이슈 

문제

다시 목록으로 돌아가면 좋아요 횟수와 좋아요를 했는지 여부가 리셋됩니다.

 

원인

boardService와 likeListService가 제대로 분리되고 해당 기능들을 수행하고 있지 않았습니다. 좋아요 관련된 로직은 전부 likeListService에서 처리할 수 있게 하고, boardService에서는 likeListService을 주입받아 처리된 정보들을 가져올 수 있게 수정해야 합니다.

 

해결

1. LikeListService에 이미 좋아요 했다면? 좋아요를 삭제한 후, board의 userLike를 get해와서 -1 한 값을 다시 set 해주는 코드를 추가해줬습니다. 그리고 업데이트 된 board를 boardRepository의 save 메서드를 통해 저장해줍니다.

 

2. LikeListService에 특정 게시글에 대해 사용자가 좋아요를 했는지의 여부와 총 좋아요 수를 조회하는 메서드를 추가해줬습니다.

HashMap인 likeInfo를 통해 좋아요 여부와 좋아요 수를 한번에 저장해줍니다. 그리고 likeInfo를 반환해줍니다.

 

3. boardService에서 게시글 상세정보를 가져올 때, likeInfo를 통해 좋아요 관련 정보를 가져올 수 있습니다. ( 물론, boardService에서 likeListService를 주입받아야 합니다! ) 그리고 이번엔 boardService의 HashMap인 response에 좋아요 정보를 put 해줍니다.

 

결과

좋아요를 하기 전 모습입니다.
isliked값이 false & 좋아요 수가 0 & 버튼이 ‘좋아요’ 인 것을 확인할 수 있습니다.

 

‘좋아요’ 를 누르면?
좋아요 수가 +1이 되고 & 버튼이 ‘좋아요 취소’ 로 바뀐 것이 확인됩니다.

 

다시 목록으로 돌아간다면?

그 전과 다르게 좋아요 수가 카운팅된대로 저장되어 보여지는 것을 확인할 수 있습니다.

 

‘좋아요’ 한 글을 다시 들어가볼까요?

isliked는 true & 좋아요 수는 1 & 버튼은 ‘좋아요 취소’ 인 것이 확인됩니다!

 

 

 


 

 

💻 [ 결과 ]  

ui까지 완성한 결과입니다!

 

'Project 댕린이집' 카테고리의 다른 글

[게시판] 댓글  (0) 2024.06.29
[게시판] 조회수  (0) 2024.06.27
[게시판] 게시글 삭제  (0) 2024.06.26
PUT vs POST  (0) 2024.06.25
[게시판] 게시글 수정(2)  (0) 2024.06.24
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함