Project/토이 프로젝트

[Cinemagram] hover 효과 및 프로필 사진 등록 - (12)

Lea Hwang 2023. 1. 26. 01:39

10. 좋아요 기능 구현

11. Popular 페이지 렌더링 포스팅에 이어 추가적으로 마우스 hover 시 좋아요 카운드를 보여주고

프로필 사진 등록 구현까지 해서 Popular페이지와 Profile페이지를 마무리 짓도록 하겠습니다. 

 

 

 

 

마우스 hover 시 좋아요 카운드 보여주기

좋아요 기능 구현에서 만든 likesCount를 여기서 활용할 수 있습니다. 

 

 

기능 구현할 수 있는 두 가지 방법 소개

이렇게 찍어내는 방법에는 두 가지가 있습니다. 

  • html에 바로 로직을 넣는 방법
  • 서버에서 로직을 짜고 html에는 결과만 넘기는 방법

 

Case 1. html에 바로 로직 넣기

<span th:text="${image.likes.size()}"></span>

 

Case 2. 서버에서 로직을 짜고 html에는 결과만 넘기는 방법

흐름 파악

UserController 

@GetMapping("/user/{pageUserId}")
public String profile(@PathVariable int pageUserId, Model model, @AuthenticationPrincipal CustomUserDetails customUserDetails) {
    UserProfileDto userProfileDto = userService.profile(pageUserId, customUserDetails.getUser().getId());
    model.addAttribute("dto", userProfileDto);
    
    return "user/profile";
}

 

UserProfileDto에 User가 있고

@Data
public class UserProfileDto {
    ...
    private User user;
}

 

 User안에 images가 있고

@Entity
public class User extends BaseTimeEntity {
    ...
    @OneToMany(mappedBy = "user")
    @JsonIgnoreProperties({"user"})
    private List<Image> images;

}

 

Image안에 likeCount가 있습니다. 이걸 활용하면 됩니다.

@Entity
public class Image extends BaseTimeEntity {
    ...
    @Transient
    @Setter
    private int likesCount;
}

 

UserService 

userEntity.getImages().forEach((image) -> {
    image.setLikesCount(image.getLikes().size());
});

html

<span th:text="${image.likesCount}"></span>

 

어느 방법을 써도 상관없습니다. 하지만 약간 귀찮더라도 로직을 서버에서 만들고 model에 담아 html는 결과만 던지는 게 좀 더 유지보수할 때 좋을 것 같다는 생각입니다. 

 

 

 

 


 

프로필 사진 등록

마지막으로는 간단하게 프로필 사진 등록을 해보겠습니다. 

 

 

고려해야 할 부분

  • pageUserId, principalId를 비교한 후 같은 경우만 동작하게 처리
  • 이미지를 put방식으로 서버로 전송

 

해당 코드 부분, profile.html

<button th:onclick="'profileImageUpload(' + ${dto.user.id} + ', ' + ${sessionId} + ')'">사진 업로드</button>

 

profile.js 

function profileImageUpload(pageUserId, sessionId) {

    if(pageUserId != sessionId) {
        alert("자신의 프로필 사진만 수정할 수 있습니다.")
        return;
    }

	...
    
    let profileImageForm = $("#userProfileImageForm")[0]; 
    let formData = new FormData(profileImageForm);        


    $.ajax({
        type: "put",
        url: "/api/user/" + sessionId + "/profileImageUrl",
        data: formData,
        contentType: false, // default : x-www-form-urlencoded로 파싱되는 것을 방지 (사진 전송 못 함)
        processData: false, // default : contentType을 false로 주면 QueryString 자동 설정되는 거 해제.
        enctype: "multipart/form-data",
        dataType: "json",
        success: function(res) {

            // 사진 전송 성공시 이미지 변경
            let reader = new FileReader();
            reader.onload = (e) => {
                $("#userProfileImage").attr("src", e.target.result);
            }
            reader.readAsDataURL(f); 

        },
        error: function(error) {
            console.log("프로필 사진 변경 오류", error);
        }
    });

}
  • form 태그 자체를 가져옴
    • let profileImageForm = $("#userProfileImageForm")[0]; 
  • FormData 객체를 이용하면 form 태그의 필드와 그 값을 나타내는 일련의 key/value 쌍을 담을 수 있습니다. 
    즉, form태그가 들고 있는 값들만 담기게 됩니다.
    • let formData = new FormData(profileImageForm);
1. form태그의 사진 데이터 전송 시
formData사용

2.  사진이 아닌 그 외 form태그의 key value 던질 때는
.serialize() 사용



하지만, 이번 토이프로젝트에서 중요한 것은 js가 아니다. 쿼리나 jpa집중

 

 

UserApiController

@PutMapping("/api/user/{sessionId}/profileImageUrl")
public ResponseEntity<?> profileImageUpdate(@PathVariable int sessionId, MultipartFile profileImageFile,
                                            @AuthenticationPrincipal CustomUserDetails customUserDetails) {
    User userEntity = userService.profileImageUpdate(sessionId, profileImageFile);
    customUserDetails.setUser(userEntity); // 세션변경
    return new ResponseEntity<>(new ResDto<>(1, "회원 프로필 사진 변경 성공", null), HttpStatus.OK);
}

 

MultipartFile

사진만 받아서 처리할 것이므로 변수에 MultipartFile을 받았습니다.

그런데 저번 ImageContoller > imageUpload에서는 MultipartFile가 아니라 데이터를 DTO로 받았습니다.

@PostMapping("/image")
public String imageUpload(@AuthenticationPrincipal CustomUserDetails customUserDetails, ImageUploadDto imageUploadDto) {
    imageService.imageUpload(customUserDetails, imageUploadDto);
    return "redirect:/user/"+customUserDetails.getUser().getId();
}
❗❗ 딱 정해줄게
보내야 할 게 두 개 이상이면 DTO
보내야 할 게 한 개면 MultipartFile를 이용합니다. 

 

 

중요한 건, MultipartFile 변수명

변수명은 해당 html의 form태그 안의 name 같아야 합니다!

<form id="userProfileImageForm">
   <input type="file" name="profileImageFile" style="display: none;"
         id="userProfileImageInput" />
</form>

 

 

 

UserService

@Transactional
public User profileImageUpdate(int sessionId, MultipartFile profileImageFile) {
    // 사진 업로드 한 부분 똑같이 ImageService > imageUpload 복사
    UUID uuid = UUID.randomUUID();
    String imageFileName = uuid+"_"+profileImageFile.getOriginalFilename();

    Path imageFilePath = Paths.get(imageUploadRoute+imageFileName);

    try{
        Files.write(imageFilePath, profileImageFile.getBytes());
    }catch (Exception e){
        e.printStackTrace();
    }

    // DB에 넣기기
    User userEntity = userRepository.findById(sessionId).orElseThrow(
            () -> new CustomApiException("유저를 찾을 수 없습니다."));
    userEntity.setProfileImageUrl(imageFileName);

    return userEntity;

} // 더티체킹으로 업데이트 됨

 

 

github에 최종적으로 push 하기 전 체크목록

  • 회원가입, 로그인
  • profile 페이지
    • 이미지 추가, 게시글 카운팅 적용
    • 회원정보 변경
    • 다른 유저 팔로우, 언팔로우(페이지 이동, 모달)
    • 로그아웃
    • /image/upload에서 profile페이지로 못 가는 이슈 발견
      • Controller에서 해당 API에 sessionId 넘기는 것으로 해결
    • 마우스 hover시 좋아요 카운트 보임
    • 프로필 사진 등록 (나의 프로필만 수정 가능)
  • feed 페이지
    • 내가 팔로우한 유저가 등록한 이미지 스크롤하면 다 보임
    • 좋아요, 좋아요 취소, 카운트 적용
  • popular 페이지
    • 좋아요 한 순으로 출력
    • 이미지 클릭 시 등록한 유저 profile페이지로 이동

 

 

 

 

 

 

 

 

여기까지 해서 드디어 좋아요 기능, Popular 페이지 렌더링, hover효과 및 프로필 사진 등록으로 push 했습니다.

다음 포스팅에는 소셜로그인을 공부 후 구현해 보도록 하겠습니다.