Spring Boot

좋아요 버튼, 좋아요 수 구현

have a good time 2021. 11. 16. 21:16

 

 

 

<jsp 파일>

           //1) 좋아요 버튼

			<button>`;
			     
			     if(image.likeState){
					item += `<i class="fas fa-heart active" id="storyLikeIcon-${image.id}" onclick="toggleLike(${image.id})"></i>`;
				}else{
					item += `<i class="far fa-heart" id="storyLikeIcon-${image.id}" onclick="toggleLike(${image.id})"></i>`;
				}
				
		
		item += `
			</button>
		</div>
        
           //2) 좋아요 수

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

좋아요 버튼, 좋아요 수 구현 

 

<ajax - js 파일 구현>

// (3) 좋아요, 안좋아요
function toggleLike(imageId) {
	let likeIcon = $(`#storyLikeIcon-${imageId}`);
	
	if (likeIcon.hasClass("far")) { // 좋아요
		
		$.ajax({
			type: "post",
			url: `/api/image/${imageId}/likes`,
			dataType: "json"
		}).done(res=>{
			
			let likeCountStr = $(`#storyLikeCount-${imageId}`).text();
			let likeCount = Number(likeCountStr) + 1;
			$(`#storyLikeCount-${imageId}`).text(likeCount);
			
			likeIcon.addClass("fas");
			likeIcon.addClass("active");
			likeIcon.removeClass("far");
		}).fail(error=>{
			console.log("오류", error);
		});
		
		

	} else { // 좋아요취소
		
		$.ajax({
			type: "delete",
			url: `/api/image/${imageId}/likes`,
			dataType: "json"
		}).done(res=>{
			
			let likeCountStr = $(`#storyLikeCount-${imageId}`).text();
			let likeCount = Number(likeCountStr) - 1;
			$(`#storyLikeCount-${imageId}`).text(likeCount);
			
			likeIcon.removeClass("fas");
			likeIcon.removeClass("active");
			likeIcon.addClass("far");
		}).fail(error=>{
			console.log("오류", error);
		});
		

	}
}

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

이 부분은, jsp파일에서, 

id = storkyLikeCount - ${image.id} 의 text() 부분, 즉

 

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

이 코드의 

${image.likeCount}

이 값을 의미한다.

 

그렇다면, 만약

이미지 10번에 대해서 id =1인 회원이 좋아요 수를 눌러서 현재, 좋아요 수가 1이라고 하자, 

즉, ${image.likeCount} =1 인 상태이다.

(현재 id=2 인 회원이 로그인 한 상태인데, 이 회원은 아직 좋아요를 누르지 않았기 때문에 하트가 빈칸이다)

 

그런데, id =2 인 회원이 이미지 10번에 좋아요를 누르면 좋아요 수가 2로 증가해야 한다.

 

그래서 코드에서 보면

 


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

 

이렇게 +1을 해줌으로써 좋아요 수를 증가시킨다.

 

** 좋아요 버튼이 빨갛게 변하는 부분은

https://happy-fun.tistory.com/142

참고

 

 

<ImageApiController.java>

	@PostMapping("/api/image/{imageId}/likes")
	public ResponseEntity<?> likes(@PathVariable int imageId, @AuthenticationPrincipal PrincipalDetails principalDetails){
		likesService.좋아요(imageId, principalDetails.getUser().getId());
		return new ResponseEntity<>(new CMRespDto<>(1, "좋아요성공", null), HttpStatus.CREATED);
	}
	
	@DeleteMapping("/api/image/{imageId}/likes")
	public ResponseEntity<?> unLikes(@PathVariable int imageId, @AuthenticationPrincipal PrincipalDetails principalDetails){
		likesService.좋아요취소(imageId, principalDetails.getUser().getId());
		return new ResponseEntity<>(new CMRespDto<>(1, "좋아요취소성공", null), HttpStatus.OK);
	}

 

중요하지는 않으나, 이해를 위해 apicontroller.java의 return에 있는 <CMRespDto.java> 파일 첨부

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
	private int code; // 1(성공), -1(실패)
	private String message;
	private T data;
}

 

<LikesService.java> 파일

 

@RequiredArgsConstructor
@Service
public class LikesService {	
	private final LikesRepository likesRepository;
	
	@Transactional
	public void 좋아요(int imageId, int principalId) {
		likesRepository.mLikes(imageId, principalId);
	}
	
	@Transactional
	public void 좋아요취소(int imageId, int principalId) {
		likesRepository.mUnLikes(imageId, principalId);
	}

 

그런데, 좋아요 수 구현은, LikesService.java가 아니라 ImageService.java에 구현했다.

<ImageService.java>

	@Transactional(readOnly = true) 
	public Page<Image> 이미지스토리(int principalId, Pageable pageable){
		Page<Image> images = imageRepository.mStory(principalId, pageable);
		
		
		images.forEach((image)->{
			
			image.setLikeCount(image.getLikes().size());
			
			image.getLikes().forEach((like) -> {
				if(like.getUser().getId() == principalId) { 
					image.setLikeState(true);
				}
			});
			
		});
		
		return images;
	}

여기서 보면, 이미지스토리 메서드에서 구현하고 있다.

그런데, ImageService.java나, LikesService.java나 같은 js파일에서 ajax를 구현해서 같은 jsp파일에 그 값들을 뿌려주기 때문에, 문제 없이 똑같은 화면에서 좋아요 상태, 좋아요 수를 나타내어줄 수 있다.

그래서 jsp파일에 위 코드의 images를 사용해서, 좋아요 수를 나타내면 된다.

 

 

그러면 먼저,

1) LikesService.java와 관련하여, 좋아요, 좋아요 취소가 구현되는 기능부터 살펴보겠다.

 

<Likes.java> 파일

 

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(
		uniqueConstraints = {
				@UniqueConstraint(
						name="likes_uk",
						columnNames = {"imageId", "userId"}
				)
		}
)
public class Likes { // N
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@JoinColumn(name = "imageId")
	@ManyToOne
	private Image image; // 1
	
	@JsonIgnoreProperties({"images"})
	@JoinColumn(name = "userId")
	@ManyToOne
	private User user; // 1
	
	private LocalDateTime createDate;
	
	@PrePersist
	public void createDate() {
		this.createDate = LocalDateTime.now();
	}


}

@Table, @UniqueConstraint 관련해서는

https://happy-fun.tistory.com/140

여기 참조

 

일단, 좋아요를 할 이미지(image), 좋아요를 누른 사용자(user) 파악을 위해 변수들을 만들었다.

 

<LikesRepository.java>

public interface LikesRepository extends JpaRepository<Likes, Integer>{

	//좋아요
	@Modifying
	@Query(value = "INSERT INTO likes(imageId, userId, createDate) VALUES(:imageId, :principalId, now())", nativeQuery = true)
	int mLikes(int imageId, int principalId);
	
    //좋아요 취소
	@Modifying
	@Query(value = "DELETE FROM likes WHERE imageId = :imageId AND userId = :principalId", nativeQuery = true)
	int mUnLikes(int imageId, int principalId);
}

좋아요를 하는 이미지 번호(imageId), 좋아요 버튼을 누르는 로그인 한 사용자(principalId)를 이용해서

쿼리를 만들어, 좋아요, 좋아요 취소 구현

 

 

2) 두번째로, 좋아요 카운트 수를 구현하겠다.

 

<Image.java>

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Image {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	private String caption; 
	private String postImageUrl;
	
	@JsonIgnoreProperties({"images"})
	@JoinColumn(name = "userId")
	@ManyToOne(fetch = FetchType.EAGER) 
	private User user; 
	
	// 이미지 좋아요
	@JsonIgnoreProperties({"image"})
	@OneToMany(mappedBy = "image") 
	private List<Likes> likes;
	
	@Transient // DB에 칼럼이 만들어지지 않는다.
	private boolean likeState;
	
	@Transient
	private int likeCount;
	
	private LocalDateTime createDate;
	
	@PrePersist
	public void createDate() {
		this.createDate = LocalDateTime.now();
	}

}

여기서 보면, @Transient 를 볼 수 있다.

likeState - true 이면 좋아요 상태, false 이면 좋아요 취소

likeCount - 좋아요 수(해당 이미지에 좋아요를 누른 수)

두 가지 변수는 db에 칼럼을 만들지 않는다.

 

위에 ImageService.java를 첨부했었는데, 설명하기 좋게 여기에 또 첨부하겠다.

<ImageService.java>

	@Transactional(readOnly = true) 
	public Page<Image> 이미지스토리(int principalId, Pageable pageable){
		Page<Image> images = imageRepository.mStory(principalId, pageable);
		
		
		images.forEach((image)->{
			
			image.setLikeCount(image.getLikes().size());
			
			image.getLikes().forEach((like) -> {
				if(like.getUser().getId() == principalId) { 
					image.setLikeState(true);
				}
			});
			
		});
		
		return images;
	}

여기에서 위의 두가지 변수인 likeState, likeCount가 쓰인다.

likeCount : 즉, image 모델에 likes 변수를 list 형태로 두어서 그 size, 즉 좋아요 수를 구한다.

likeState : 즉, image 모델의 likes 변수의 각 like 값에대해서,

좋아요 버튼을 누른 사용자와, 현재 로그인한 사용자가 같다면,

likeState = true가 된다.

 

위의 코드에서 imageRepository 관련한 코드가 있는데, 이는 사용자가 웹사이트에 올린 이미지를 db에서 가져오는 내용이다

 

 

이렇게 하면 좋아요 구현, 좋아요 수 구현이 완성된다. 

 

참고자료 : 이지업 강의 사이트 "스프링부트 SNS프로젝트 - 포토그램 만들기"