좋아요 버튼, 좋아요 수 구현
<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프로젝트 - 포토그램 만들기"