Super Kawaii Cute Cat Kaoani [인스타그램 클론 코딩][Spring boot] 10. 좋아요 및 인기 페이지 구현

[인스타그램 클론 코딩][Spring boot] 10. 좋아요 및 인기 페이지 구현

2024. 2. 23. 01:11
728x90
SMALL

⚡️ 좋아요 구현

 

📌 Likes 엔티티 구현

package yerong.InstagramCloneCoding.domain.likes;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.*;
import yerong.InstagramCloneCoding.domain.BaseTimeEntity;
import yerong.InstagramCloneCoding.domain.image.Image;
import yerong.InstagramCloneCoding.domain.user.User;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Builder
@Entity
@Table(
        uniqueConstraints = {
                @UniqueConstraint(
                        name = "likes_uk",
                        columnNames = {"image_id", "user_id"}
                )
        }
)
public class Likes extends BaseTimeEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "likes_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "image_id")
    private Image image;

    //User를 가져오면 안에 있는 image도 가져오게 돼서 image의 정보는 가져오지 않게 해야 함
    @JsonIgnoreProperties("images")
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "user_id")
    private User user;



}
  • Like가 MariaDB, MySQL에서 키워드로 쓰이기 때문에 테이블이 안 만들어진다. ➡️ likes
  • 하나의 이미지엔 여러 좋아요가 달릴 수 있음
  • 하나의 유저는 여러 게시글의 좋아요를 누를 수 있음
    • 좋아요에서 user를 가져오면 user와 연관관계가 설정된 image도 가져오게 돼서 이 image를 가져오지 못하게 해야 함

 

 

📌 Likes Repository

package yerong.InstagramCloneCoding.repository.likes;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import yerong.InstagramCloneCoding.domain.likes.Likes;

public interface LikesRepository extends JpaRepository<Likes, Long> {

    @Modifying
    @Query(value = "INSERT INTO likes(image_id, user_id, createdAt) VALUES (:imageId, :principalId, now())", nativeQuery = true)
    int mLikes(Long imageId, Long principalId);

    @Modifying
    @Query(value = "DELETE FROM likes WHERE image_id = :imageId AND user_id = :principalId", nativeQuery = true)
    int mUnLikes(Long imageId, Long principalId);
}
  • 좋아요 및 좋아요 취소를 nativeQuery를 통해서 구현
    • 생성 시간을 못가져오므로 insert 쿼리에서 같이 날려야 한다

 

 

📌 LikeServiceImpl

package yerong.InstagramCloneCoding.service.impl;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import yerong.InstagramCloneCoding.domain.image.Image;
import yerong.InstagramCloneCoding.domain.user.User;
import yerong.InstagramCloneCoding.handler.exception.CustomException;
import yerong.InstagramCloneCoding.handler.exception.CustomValidationApiException;
import yerong.InstagramCloneCoding.repository.image.ImageRepository;
import yerong.InstagramCloneCoding.repository.likes.LikesRepository;
import yerong.InstagramCloneCoding.repository.user.UserRepository;
import yerong.InstagramCloneCoding.service.LikeService;

@Service
@RequiredArgsConstructor
public class LikeServiceImpl implements LikeService {

    private final LikesRepository likesRepository;

    @Transactional
    @Override
    public void like(Long imageId, Long principalId){
        likesRepository.mLikes(imageId, principalId);
    }
    @Transactional
    @Override
    public void unlike(Long imageId, Long principalId){
        likesRepository.mUnLikes(imageId, principalId);
    }

}

 

 

 

📌 ImageApiController

    @PostMapping("/api/image/{imageId}/likes")
    public ResponseEntity<CMRespDto<?>> imageLikes(
            @PathVariable Long imageId,
            @AuthenticationPrincipal PrincipalDetails principalDetails
    ){
        likeService.like(imageId, principalDetails.getUser().getId());
        return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(1, "좋아요 성공", null), HttpStatus.CREATED);
    }
    @DeleteMapping("/api/image/{imageId}/likes")
    public ResponseEntity<CMRespDto<?>> imageUnLikes(
            @PathVariable Long imageId,
            @AuthenticationPrincipal PrincipalDetails principalDetails
    ){
        likeService.unlike(imageId, principalDetails.getUser().getId());
        return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(1, "좋아요 취소 성공", null), HttpStatus.OK);
    }
  • 이 사진의 id와 로그인된 user의 id가 필요함

 

 

⚡️ 인기 페이지 구현

  • 좋아요가 많은 순서대로 출력

📌 PopularImageDto

package yerong.InstagramCloneCoding.web.dto.image;

import lombok.*;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter

public class PopularImageDto {
    private Long id;
    private String caption;
    private String postImageUrl;
    private Long userId;
    private String username;
    private String profileImageUrl;

}

 

📌 ImageServiceImpl

 @Override
    public List<PopularImageDto> popularImage(){
        List<Image> images = imageRepository.mPopular();
        return images.stream().map(image -> {
            return PopularImageDto.builder()
                    .id(image.getId())
                    .caption(image.getCaption())
                    .postImageUrl(image.getPostImageUrl())
                    .userId(image.getUser().getId())
                    .username(image.getUser().getUsername())
                    .profileImageUrl(image.getUser().getProfileImageUrl())
                    .build();
        }).collect(Collectors.toList());

    }

 

📌 ImageController

    @GetMapping("/image/popular")
    public String popular(
            @AuthenticationPrincipal PrincipalDetails principalDetails,
            Model model
    ){
        List<PopularImageDto> popularImageDtos = imageService.popularImage();
        model.addAttribute("popularDtos", popularImageDtos);
        model.addAttribute("principal", principalDetails.getUser());

        return "views/image/popular";
    }

 

⚡️ User 프로필 페이지 좋아요 구현

➡️ 이렇게 프로필에서 사진에 마우스를 뒀을 때 보이는 좋아요 개수 구현

 

📌 UserServiceImpl

 

    @Override
    @Transactional
    public UserProfileDto profile(Long pageUserId, Long pageOwnerId){

        UserProfileDto dto = new UserProfileDto();
        User pageUser = userRepository.findById(pageUserId).orElseThrow(() -> new CustomException("해당 프로필 페이지는 없는 페이지입니다."));

        dto.setUser(pageUser);
        dto.setImageCount(pageUser.getImages().size());
        dto.setPageOwnerState(pageOwnerId.equals(pageUserId));
        int state = subscribeRepository.mSubscribeState(pageOwnerId, pageUserId);
        int count = subscribeRepository.mSubscribeCount(pageUserId);

        dto.setSubscribeState(state==1);
        dto.setSubscribeCount(count);
        
        userEntity.getImages().forEach((image)->{
        	image.setLikeCount(image.getLikes().size()));
        });
        return dto;
    }

 

  • 이런 식으로 for문으로 image를 찾아서 해당 좋아요수를 하나하나 구하는 방법

 

 

⚡️ 프로필 사진 등록

➡️ 로그인된 회원만 프로필 사진 변경이 되어야 한다.

 

📌 UserApiController

  @PutMapping("/api/user/{principalId}/profileImageUrl")
    public ResponseEntity<CMRespDto<?>> changeProfileImage(
            @PathVariable Long principalId,
            MultipartFile profileImageFile,
            @AuthenticationPrincipal PrincipalDetails principalDetails
    ){
        User user = userService.changeProfileImage(principalId, profileImageFile);
        principalDetails.setUser(user);
        return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(1, "프로필 사진 변경 성공", null), HttpStatus.OK);
    }

 

 

📌 UserServiceImpl

    @Value("${file.path}")
    private String uploadFolder;

    @Override
    @Transactional
    public User changeProfileImage(Long principalId, MultipartFile profileImageFile){
        UUID uuid = UUID.randomUUID();
        String imageFileName = uuid + "_" + profileImageFile.getOriginalFilename();
        Path imageFilePath = Paths.get(uploadFolder+imageFileName);
        try{
            Files.write(imageFilePath, profileImageFile.getBytes());
        }
        catch (Exception e){
            e.printStackTrace();
        }

        User user = userRepository.findById(principalId).orElseThrow(() -> new CustomValidationApiException("찾을 수 없는 Id 입니다."));
        user.setProfileImageUrl(imageFileName);

        return user;
    }

 

  • 게시글을 등록할 때와 똑같은 방법으로 imageUrl을 등록해준다.

 

📌 User

    public void setProfileImageUrl(String profileImageUrl){
        this.profileImageUrl = profileImageUrl;
    }
  • User 엔티티에 profileImageUrl을 설정해주는 코드를 추가해준다

 

끄읏!

 


PREV

 

[인스타그램 클론 코딩][Spring boot] 9. 스토리(메인) 페이지

✅ 필요한 것 게시글을 올린 user 정보 (이름, 프로필사진) 사진에 대한 정보 캡션에 대한 정보 좋아요, 댓글에 대한 정보 (좀 뒤에 구현 예정) 📌 ImageDto package yerong.InstagramCloneCoding.web.dto.image; impor

nyeroni.tistory.com

NEXT

 

[인스타그램 클론 코딩][Spring boot] 11. 댓글 구현

📌 Comment Entity 구현 package yerong.InstagramCloneCoding.domain.comment; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.*; import yerong.InstagramCloneCoding.domain.BaseTimeEntity; import yer

nyeroni.tistory.com

 

728x90
SMALL

 

728x90
LIST

BELATED ARTICLES

more