ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링부트+JPA - 인스타그램 클론 코딩 12 - 기타(인기페이지/좋아요카운트/프로필 유저 사진 변경)
    스프링/스프링부트+JPA - 인스타 2021. 10. 18. 19:27

    이지업 최주호 강사님 강의 참조

     

     

     

    01 인기페이지 구현(좋아요가 높은순으로 출력)

    이 화면에서 이제 좋아요 순으로 나열할 예정

    • 저 페이지를 호출하는 컨트롤러에서 데이터를 들고 호출해야한다. 
      public class ImageController {
      	...
      	@GetMapping("/image/popular")
      	public String popular(Model model) {
      		List<Image> images = imageService.popularPics();
      		model.addAttribute("images", images);
      		return "image/popular";
      	}​
      	...
       APIController에 안만드냐는 질문이 있을 수 있다. 모든 API 컨트롤러는 @RestController가 달려있고 데이터를 리턴하는 컨트롤러들을 구현해놓은 것이다. 보통 이 리턴받은 데이터로 AJAX등을 구현하기위해 API를 쓴다. 현재는 단순 데이터를 들고 호출 + JSP를 리턴하는것이기때문에 API에서 세팅을 하지 않는다. (API로 구현이 가능하기는 하다. 하지만 이런 단순 호출에 데이터를 리턴하면 JS를 써야하고 쓸데없이 어렵게 구현해야하는 번거로움이 있다. 또는 안디로이드/IOS등에서 요청하는것은 HTML로 못주고 데이터로 주기때문에 반드시 API로 해야한다.)

     

    • imageService.popularPics() 추가
      public class ImageService {
      	...
          	//인기페이지 호출시 좋아요 순
      	@Transactional(readOnly = true)
      	public List<Image> popularPics(){
      		return imageRepository.mPopular();
      	}​
    • imageRepository.mPopular() 추가
    1. 레파지토리에서 nativeQuery를 구현하기전에 직접 연습을 해보는 과정이 중요하다
      먼저 image/likes의 DB 상태는 아래와 같다
      image
      likes
    2. 여기서 image를 뽑는데 좋아요가 된 이미지만 뽑는 쿼리를 보자
      SELECT imageId FROM likes;
      SELECT * FROM image WHERE id IN(SELECT distinct imageId FROM likes);​

      중복을 제거하고 하나씩 들고오게되지만 보시다싶이 3이 더 많은데 2,3 순으로 들고온다. 즉 distinct는 안된다는 말

    3. 가상컬럼 + GROUP BY로 처리해주자 
      SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId ORDER BY likeCount DESC;

      카운터 + 그룹화로인해서 중복된거는 카운터처리되서 어느게 얼마나 가지고 있는지를 볼 수 있다. 
      그 이후 오더바이로 처리하면 원하는 쿼리가 나온다. 
    4.  마지막으로 인라인뷰를 써서 테이블을 하나로 만들고 IN에 넣어주면 되는데 IN의 특성상 정렬이 되지 않기때문에 3번의 쿼리를 조인시키고 아이디와 image아이디를 같은것으로 테이블을 만들고 likeCount로 정렬하면 된다. 
      SELECT * FROM image i INNER JOIN
      (SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c
      ON i.id = c.imageId ORDER BY likeCount DESC;

       
    5. 이제 4번의 쿼리를 레퍼지토리에 넣으면 되는데 이미지를 호출할때 Image.java 클래스는 imageId + likeCount를 들고 있지 않기때문에 오류가 발생한다. 그래서 표현식을 좀 바꿔 i 값만 들고오면 된다(i값만 들고와도 4번에서 우리가 구하고자하는 좋아요 순으로 뽑힌다) 
      SELECT i.* FROM image i INNER JOIN
      (SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c
      ON i.id = c.imageId ORDER BY likeCount DESC;​
       모든 테이블이 아닌 i.*을 써서 image의 id와 관련된 테이블만 들고 오도록 변경
    • 레파지토리에 넣어보자
      public interface ImageRepository extends JpaRepository<Image, Integer>{
      	..
          	@Query(value = "SELECT i.* FROM image i INNER JOIN(SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c ON i.id = c.imageId ORDER BY likeCount DESC", nativeQuery = true)
      	List<Image> mPopular();
      }​

     

    • popular.jsp에 뿌리기
      <!--인기 게시글-->
      <main class="popular">
      <div class="exploreContainer">
      <!--인기게시글 갤러리(GRID배치)-->
      <div class="popular-gallery">
      <c:forEach var="image" items="${images}">
      <div class="p-img-box">
      <a href="/user/${image.user.id}"> <img src="/upload/${image.postImageUrl}" />
      </a>
      </div>
      </c:forEach>
      </div>
      </div>
      </main>
      <%@ include file="../layout/footer.jsp"%>
      각각의 사진을 클릭하면 이 사진을 올린 유저로 이동 된다!
    더보기
    여기에서 <img src="/upload/${image.postImageUrl}" /> 을 들고온다

    <a href="/user/${image.user.id}"> -> 이미지를 올린 유저로 이동

     

     

     

     

    02 프로필페이지 좋아요 카운트

     

    • 이미 좋아요를 구현했기때문에 그 구현한 것에 값을 그대로 들고와서 뿌리기만 하면 된다.
    • 유저 프로필이기때문에 User를 호출하면 User.java가 들고있는 Image가 불러와지고 그 Image.java가 들고있는 likeCount를 그대로 쓰면 된다

     

    • 서비스에서 호출시에 카운터를 넣어서 계산이 되도록 하고
    public class UserService {
          ...
          @Transactional(readOnly = true)
          public UserProfileDto profile(int pageUserId, int principalId) {
         	...
            userEntity.getImages().forEach((image)->{
           		image.setLikeCount(image.getLikes().size());
        	});
    
        return dto;
    }​
    • JSP에서 받아주면 된다. 
      <a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likeCount}</span>​

     

    번외로 그냥 외부(JSP)에서 계산을 해도 된다

    더보기

    <a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likes.size()}</span>

     

     

     

    03 프로필 사진 수정

    동그라미 안에 이미지를 바꿔볼 예정

     

    구현해야할 기능은 두가지 이다.

    1) 유저가 로그인해서 이미지를 바꾸면 그 바꾼 이미지로 등록 되도록.

    2) 1번으로 로그인했는데 2번으로 들어와서 1번을 못바꾸도록 막기.

     

     

    • 현재 저 사진이미지를 클릭하면 사진을 변경할 수 있도록 되어있다. 그 코드는 아래와 같다

    profile.jsp

    → 유저 이미지를 클릭하게되면 저 부분이 발동하게 되고 'id="userProfileImageInput"' 함수가 아래와 같이 발동 된다

        함수가 발동되고 이미지는 변하지면 새로고침하면 원상복구 된다. 여기서 이제 DB에 넣는 과정을 구현 할 예정

    profile.js

     

     

    DB에 넣을때 막넣으면 안되고 로그인 유저와 페이지 유저가 같아야만 이미지가 변경이 되도록하고 그 값이 DB로 들어가야 한다.

    1. profile.js 에서 파라미터 받기
    2. JSP에서 저 두 파라미터를 날리도록 세팅
      로그인 유저는 쉽게 들고 오지만 유저의 아이디를 어떻게 들고올지를 모르겠으면 위에 코드가 어떻게 뿌려지는지 보면 쉽게 찾을 수 있다!
        
    3. → 테스트
    4.  

    3. 저 두값을 비교 해서 값이 다르면 아예 이미지 업로드가 안뜨도록 세팅

     

    4. 이제 같은유저이면 수정이 되고 수정된 데이터가 (이미지)를 서버에 전송해줘야 한다. 

    • 변경된 이미지는 위의 폼 태그에 담긴다
    • profile.js에서 서버에 바뀐 이미지를 전송할 코드를 구성 하자

    0번째를 들고 와야하는걸 볼 수 있다.
    0번째
    이 데이터를 보내야한다.

    • 이제 위에서 축출된 데이터를 보내야하는데 보통 ajax를 쓰지 않으면 submit으로 보내면 되지만 현재 ajax로 처리를 하기 때문에 데이터를 form 객체로 감싸야 한다. 

     

    • 이제 이 코드를 실행할 API 구축 (UserApiController)
    	@PutMapping("/api/user/{principalId}/profileImageUrl")
    	public ResponseEntity<?> profileImageUrlUpdate(@PathVariable int principalId, MultipartFile profileImageFile,
    			@AuthenticationPrincipal PrincipalDetails principalDetails){
    		User userEntity = userService.changeProfileImage(principalId,profileImageFile);
    		principalDetails.setUser(userEntity);
    		return new ResponseEntity<>(new CMRespDto<>(1, "프로필 이미지 변경 성공", null),HttpStatus.OK);
    		
    	}
    1. 호출시에 principalId와 파일내용을 같이 넘겨줘야하므로 파라미터에 principalId/profileImageFile를 받고 거기에 세션체크를 해야함으로 @AuthenticationPrincipal PrincipalDetails principalDetails까지 넣어 주자
    2. 파일에 대한 파라미터 이름은 input되는 곳에서 선언된 "name"과 반드시 같아야 오류가 없다
    3. 사진 변경 후 변경된 사진 및 회원의 세션이 똑같이 변경 되야 하므로 서비스 호출을 객체화 시키고 그 바뀐 호출 객체로 세션을 바꿔주면 되는 것. 
    • UserService에 함수 추가
    public class UserService {
    	..
    	@Value("${file.path}")
    	private String uploadFolder;
    	
    	
    	@Transactional
    	public User changeProfileImage(int principalId, MultipartFile profileImageFile) {
    		UUID uuid = UUID.randomUUID();
    		String imageFileName = uuid+"_"+profileImageFile.getOriginalFilename(); //실제 파일 이름
    //		System.out.println("파일 이름: "+imageFileName);
    		
    		Path imageFilePath = Paths.get(uploadFolder+imageFileName);
    		
    		try {
    			Files.write(imageFilePath, profileImageFile.getBytes());
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		User userEntity = userRepository.findById(principalId).orElseThrow(()->{
    			throw new CustomApiException("유저를 찾을 수 없습니다.");
    		});
    		
    		userEntity.setProfileImageUrl(imageFileName);
    		return userEntity;
    	}

    → try/catch까지 정상적으로 타면 사진은 서버에 저장이 된다. 그 이후 서버에 저장된 사진을 DB에 넣는 작업을 해줘야 한다. 첫 User UserEntity로 영속화를 시킨 후 맨 마지막 문장에서 'userEntity.setProfileImageUrl(imageFileName);'로 DB값을 바꾸면 된다. 

     

    • JSP에 마지막으로 호출

      굿!

     

Designed by Tistory.