Web/Spring

[Spring] 스프링부트 + JPA + thymeleaf + Spring Security로 회원가입 + 게시판(수정, 삭제, 등록, 조회, 댓글, 좋아요) + 로그인 구현하기 #11 좋아요 기능(중복 방지)

poopooreum 2024. 8. 31. 22:00
반응형

좋아요 누르기 로직

  1. 로그인 한 유저는 아무 게시물에 좋아요를 누를 수 있다.
  2. 한 사람이 같은 게시물에 여러 번 좋아료를 누르는 것을 방지한다.
  3. 방지하는 방법은 좋아요를 한 번 누르면 좋아요 수가 증가하고, 거기서 한 번더 누르면 좋아요 수가 감소한다.
  4. 인스타에서 우리가 좋아요를 누르면 하트가 빨간색으로 변하고, 다시 누르면 하트에 빨간색이 사라지는 것을 생각하기

✏️ Likes 클래스

package hello.hello_spring.domain.board;

import jakarta.persistence.*;
import lombok.*;

@Getter
@Setter
@Entity
@Table(name = "Likes")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Likes {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idx", nullable = false)
    private Integer id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "boardIdx", nullable = false)
    private Board boardIdx;

    @Column(name = "memberId", nullable = false, length = 20)
    private String memberId;

}

Likes DB와 구조가 동일하다


✏️ Api Controller 클래스

@PostMapping("/board/{idx}/like")
public String commentLike(@PathVariable("idx") Integer idx, HttpServletRequest httpServletRequest) {
    HttpSession session = httpServletRequest.getSession(false);
    Optional<Object> idOptional = Optional.ofNullable(session.getAttribute("userId"));
    if (idOptional.isEmpty()) {
        log.error("권한 없음");
        return "redirect:/login";
    }

    String memberId = idOptional.get().toString();
    boolean isBoardExistCheck = likesService.checkBoardExist(idx);
    if (!isBoardExistCheck) {
        log.error("게시글이 존재하지 않음");
        return "redirect:/member/list";
    }

    boolean isMemberIdExistCheck = likesService.checkMemberIdExist(memberId);
    if (!isMemberIdExistCheck) {
        log.error("좋아요를 누를 수 있는 권한이 없음");
        return "redirect:/member/list";
    }

    boolean isLikeOverlap = likesService.checkLikeExist(idx, memberId);
    if (isLikeOverlap) { // 좋아요 중복이 아닌 경우
        log.error("좋아요를 중복해서 좋아요 개수 차감");
        likesService.minusLike(idx,memberId);
        return "redirect:/member/list";
    }

    likesService.addLike(idx, memberId);
    log.info("좋아요 성공");
    return "redirect:/member/list";
}

로그인이 되어 있는지 검사한다

url로부터 전달받은 idx를 통해 게시물이 존재하는지 검사한다

세션에서 얻은 id가 Member에 존재하는지 검사한다

위의 조건을 모두 만족시키면 idx와 memberId를 통해 Likes를 검사한다(Likes에는 게시물의 번호와 좋아요를 누른 사람이 저장)

만약 Likes에 존재한다면 좋아요를 중복해서 누르는 것이므로 좋아요 개수를 1차감

그게 아니라면 좋아요 개수를 1추가하고 Likes DB에 idx와 memberId를 저장


✏️ LikeRepository 인터페이스

public interface LikesRepository extends JpaRepository<Likes, Long> {
   List<Likes> findByMemberIdAndBoardIdx(String memberId, Board board);
   void deleteByMemberIdAndBoardIdx(String memberId, Board board);
}

✏️ LikeService 인터페이스

public interface LikesService {
    boolean checkLikeExist(Integer idx,String memberId);
    boolean checkBoardExist(Integer idx);
    boolean checkMemberIdExist(String memberId);
    void addLike(Integer idx,String memberId);
    void minusLike(Integer idx, String memberId);
}

✏️ LikeServiceImpl 클래스

@Service
@Slf4j
@RequiredArgsConstructor
public class LikesServiceImp implements LikesService {

    @Transactional
    @Override
    public void minusLike(Integer idx, String memberId) {
        Optional<Board> boardOptional = boardRepository.findById(idx.longValue());
        Board board = boardOptional.get();
        if(board.getLikeCount()>=1) {
            board.setLikeCount(board.getLikeCount() - 1);
        }
        boardRepository.save(board);
        likesRepository.deleteByMemberIdAndBoardIdx(memberId,board);
    }

    @Override
    public void addLike(Integer idx,String memberId) {
        Optional<Board> boardOptional = boardRepository.findById(idx.longValue());
        Board board = boardOptional.get();
        board.setLikeCount(board.getLikeCount() + 1);
        boardRepository.save(board);

        Likes likes = Likes.builder()
                .boardIdx(board)
                .memberId(memberId)
                .build();
        likesRepository.save(likes);
    }

    private final LikesRepository likesRepository;
    private final MemberRepository memberRepository;
    private final BoardRepository boardRepository;

    @Override
    public boolean checkBoardExist(Integer idx) {
        Optional<Board> boardOptional = boardRepository.findById(Long.valueOf(idx));
        return boardOptional.isPresent();
    }

    @Override
    public boolean checkMemberIdExist(String memberId) {
        Optional<Member> memberOptional = memberRepository.findById(memberId);
        return memberOptional.isPresent();
    }

    @Override
    public boolean checkLikeExist(Integer idx, String memberId) {
        Optional<Board> boardOptional = boardRepository.findById(Long.valueOf(idx));
        Board board = boardOptional.get();
        List<Likes> likesList = likesRepository.findByMemberIdAndBoardIdx(memberId, board);
        return !likesList.isEmpty();
    }
}

minusLike

  • 전달받은 idx를 통해 게시물을 가지고 온다
  • 만약 게시물의 좋아요가 1보다 크거나 같다면 좋아요 수를 -1한다
  • 게시물의 데이터값을 변경햇으므로 boardRepository.save()를 통해 변경값을 저장한다
  • memberId와 idx를 통해 Likes DB를 삭제(그래야지 다음번에 좋아요를 누르면 다시 좋아요 수가 증가)

addLike

  • 전달받은 idx를 통해 게시물을 가지고 온다
  • 게시물의 좋아요 수를 1증가시킨다.
  • 게시물의 데이터값을 변경햇으므로 boardRepository.save()를 통해 변경값을 저장한다
  • 중복 방지를 위해서 Likes에 전달받은 idx와 memberId를 Likes DB에 저장한다.

checkBoardExist

  • 전달받은 idx를 통해 게시물의 존재 여부를 검사한다.

checkMemberIdExist

  • 전달받은 memberId를 통해 Member에 존재하는지 id인지 검사한다.

checkLikeExist

  • 전달받은 idx를 통해 게시물을 가져온다
  • 전달받은 memberId와 위에서 가져온 게시물을 통해 Likes에 존재하는지 검사한다.

반응형