Project/토이 프로젝트

[Cinemagram] 좋아요 기능 구현 - (10)

Lea Hwang 2023. 1. 25. 16:47

 좋아요 기능 구현, Popular 페이지 렌더링을 연달아 구현할 예정입니다.

 

 

 

Popular 페이지란?

좋아요가 많은 순서대로 Image를 출력하는 페이지입니다.

 

따라서 그전에 좋아요 기능을 먼저 구현해 보도록 하겠습니다.

 

 

 

좋아요 기능 구현

모델 만들기 & 연관관계 매핑

엔티티명은 MYSQL에 LIKE 함수가 존재하므로 Likes로 만들었습니다.

😁 @Transient
엔티티 필드로는 필요한데, DB 칼럼으로 만들고 싶지 않을 때 사용하는 어노테이션

 

 

어떤 필드들이 필요할까?

우리가 SNS에서 어떤 로직으로 좋아요를 누르는지 생각해보면 됩니다.

즉, 어떤 이미지를 누가 좋아했는지 알아야 합니다. 

또한 특정 유저는 한 이미지를 딱 한 번만 좋아요 누를 수 있습니다. (다중 칼럼 UNIQUE 제약 조건)

- 좋아요 누르고 다시 누르면 좋아요 취소되게끔 JS처리

 

 

Likes

@NoArgsConstructor
@Getter
@Entity
@Table(
        uniqueConstraints = {
                @UniqueConstraint(
                        name = "likes_uk",
                        columnNames = {"imageId", "userId"}
                )
        }
)
public class Likes extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @JoinColumn(name = "imageId")
    @ManyToOne(fetch = FetchType.LAZY)
    private Image image;

    @JoinColumn(name = "userId")
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

}

 

모델과 Repository는 짝꿍으로 함께 만들어줍니다.

LikesRepository 

public interface LikesRepository extends JpaRepository<Likes, Integer> {
}

 

이제 LikesRepository를  Service에 연결합니다.

 

그리고 Controller를 만들면 되는데, 

어떤 Controller를 만들어야 할까요?

  • Controller
  • ApiController

 

 

우리는 API 호출을 통해 데이터를 받을 것이므로 ApiController,  그중에서도 이미 만든 이미지 컨트롤러를 사용할 것입니다. (재사용성)

 

 

만들 메서드

  • 좋아요
  • 좋아요 취소

 

해당 메서드 API 주소

  • 이미지 / 어떤 이미지를 / 좋아요 한 건지
    • @PostMapping("/api/image/{imageId}/likes")
  • 이미지 / 어떤 이미지를 / 좋아요 취소 한 건지
    • @DeleteMapping("/api/image/{imageId}/likes")

 

로직

  • ApiController에서 서비스를 호출
    • 어떤 이미지를, 누가 좋아하는지 알아야 하므로 파라미터에 ImageId, sessionId 넘김
  • 서비스에서 리포지토리를 호출
    • 리포지토리에서 nativeQuery 직접 구현

 

👏 이 정도 쿼리는 생성할 수 있도록 연습해야 한다.
좋아요
INSERT INTO likes(imageId, userId) VALUES(:imageId, :sessionId)

좋아요 취소
DELETE FROM likes WHERE imageId =:imageId AND userId =:sessionId

 

 

Postman 확인

로직을 잘 탔는지 Postman으로 확인해 보겠습니다. (로그인 : 2)

 

좋아요 기능 확인

 

좋아요 기능 취소 확인

 

 

 

브라우저에서 확인 (JS)

Postman으로 좋아요 기능과 취소기능을 확인했습니다.

이제 브라우저에서도 확인할 수 있게 html, js 처리를 해주면 됩니다. 

 

목표

  • 로그인하면 Feed페이지좋아요 한 이미지만 빨간 하트처리

 

로직

  • Feed페이지 랜더링 API를 호출할 때, images에 좋아요 정보를 추가로 담아야 합니다. 
    • 관련 API : ImageApiController의 @GetMapping("/api/feed")
    • Image엔티티에 likes 양방향 연관관계 설정 (@OneToMany)
      • 좋아요 카운트, 좋아요 상태를 담을 필드 추가 생성 (likesCount, likesState)
      • DB에 굳이 컬럼을 생성 안 해도 되므로 상단 @Transient 기재
    • 좋아요 정보 담기
👍 좋아요 정보를 어떻게 담을 건지 생각해 볼 것

- 상황 : 로그인(1번) 하면 Feed페이지에 내(1번)가 구독하는 사람들이 올린 이미지가 출력됨

- 목표 : 이미지 좋아요, 이미지 카운트 정보 뽑아서 images에 담기

- 어떻게 :  
1. Feed에 있는 모든 이미지를 돌면서 (for 문)
2. 이미지의 좋아요 정보 뽑기
3. 그중 내가(1번) 좋아요 한 게 있다면 likesState=true 후 그 정보를 images에 넣기
4. 좋아요 카운트는 이미지의 좋아요 사이즈를 통해 세팅

 

 

이를 적용한 ImageService

@Transactional(readOnly = true)
public Page<Image> Feed(int sessionId, Pageable pageable) {
    Page<Image> images = imageRepository.cFeed(sessionId, pageable);

    images.forEach((image -> {
        image.setLikesCount(image.getLikes().size());

        image.getLikes().forEach((like) -> {
            if(like.getUser().getId() == sessionId) { 
                image.setLikesState(true);
            }
        });
    }));

    return images;
}

 

 

 

😁 중간점검 😁

1. 추후 리팩토링 해야 할 부분 (Image 엔티티)
현 상황
@Data 사용을 지양하기 위해 엔티티 상단에 있던 것을 지우고 변경이 필요한 필드 위에만 @Setter를 걸어둠

리팩토링 방향
@Setter를 지우고 DTO를 새로 만들어서 처리


2. 기억할 것
@XToOne은 default가 EAGER이므로 →  LAZY 수정 
@XToMany default가 LAZY


3. 무한 순환참조 이슈가 있는지 항상 확인 (발생한 Case별로 나눠서 포스팅함)

 

 

 

 

 

 

이어서 진행해 보겠습니다. 이제 브라우저 상에서 인터렉티브 한 부분을 다룰 차례입니다. (JS)

 

목표

하트를 눌렀을 때 좋아요가 되면서 카운트되도록 하기

 

 

html에서 해당 코드 부분 (id 주목)

<span class="like"><b id="storyLikeCount-${image.id}">${image.likeCount}</b>likes</span>

 

JS로 와서 하트를 누르는 함수에 위의 id를 가져와서 적용해 주면 됩니다.

$.ajax({
    type: "post",
    url: `/api/image/${imageId}/likes`,
    dataType: "json",
    success: function(res) {

        let likeCountStr = $(`#storyLikeCount-${imageId}`).text();
        let likeCount = Number(likeCountStr) + 1;
        $(`#storyLikeCount-${imageId}`).text(likeCount);

        likeIcon.addClass("fas");
        likeIcon.addClass("active");
        likeIcon.removeClass("far");
    },
  • let likeCountStr = $(`#storyLikeCount-${image.id}`).text();
    • 아이디 #storyLikeCount-${image.id}의 text를 가져와서 저장합니다.
  • let likeCount = Number(likeCountStr) - 1;
    • likeCountStr이 문자열이므로 Number로 캐스팅
  • $(`#storyLikeCount-${image.id}`).text(likeCount);
    • text 자리에 넣습니다. 


 

 

 

 

 

 

 

 

 

 

 

 

원래 좋아요 기능, Popular 페이지 렌더링, 자잘한 부분을 전부 한 포스팅에 기재하려고 했습니다. 

자세하게 적느라 너무 길어져 총 3편으로 나누게 되었고 전부 합쳐서 github에 올리겠습니다. 

읽어주셔서 감사합니다😁