티스토리 뷰

스파르타코딩클럽 미니프로젝트 회고

미니프로젝트 "소소" 깃허브 구경하러 가기


미니프로젝트로 "소소"한 갤러리형 게시판을 프론트엔드와 협업하여 만들어보았다.
처음으로 프론트엔드와 협업하는 프로젝트여서 하나부터 열까지 쉬운 것이 없었다.
지나고보면 당연한 것을 당시에는 놓쳐서 하루를 꼬박 날리기도 하고 백엔드끼리도 깃과 깃허브 사용법이 익숙치 않아서 헤매기도 했다.
그만큼 배운 것도 많고 느낀 것도 많았던 프로젝트였다고 할 수 있겠다.


API 명세서의 중요성을 다시금 깨달았다.

  • API 명세서의 이름과 요소가 매우 중요하고 처음에 명세서를 짤 때 깊이 고민해보고 짜야할 것 같다. 그리고 그 명세서대로 이름을 붙이고 적절한 요소들을 request, response하는 것이 중요하다고 느꼈다.
  • 서로 이름이 달라서 오류가 났던 것도 있고 API 명세서를 짜면서 생각하지 못해서 변경해야 했던 부분도 있었다.
  • token의 이름, 기호, 대소문자 여부 때문에 하루를 꼬박 날리고서 팀프로젝트를 할 때는 정해진 대로 진행하는 것이 중요하다고 느꼈다.

Git과 GitHub 공부의 필요성을 느꼈다.

  • 처음에 팀 Repository를 생성하고 클론하지 않고 각자 마음대로 폴더를 만들고 이름도 일관성이 없다보니 나중에 git을 원격으로 이용하려고 할 때 서로 관련성이 없다는 오류 때문에 어려움이 많았다.
  • 특히 폴더경로가 다르면 pull 오류가 뜨기도 하고 강제로 pull 하더라도 합쳐지지가 않아서 일일이 복붙하며 시간을 버리게 되었다.
  • 처음 Repository를 생성하고 entity를 만들고 대략적인 패키지와 파일을 구성한 후에 다같이 클론해서 진행하는 것이 좋을 것 같다는 생각이 들었다.

코드 복붙의 폐해, 코드 일관성의 중요성을 깨달았다.

  • 내가 맡은 부분은 코드를 직접 손으로 쳐서 짜보고 싶었다. 물론 오로지 내 머리와 손으로만 짠 코드는 아니지만 여기저기서 참고하고 직접 타이핑하여 코드를 작성했고 프로젝트에서 해결해야 됐던 부분은 소소하지만 스스로 짜며 해결해보기도 하였다. 그 코드는 수정이 필요하다며 지적을 당했지만 그래도 어찌되었건 기능하게끔 스스로 구현한 것에 의의를 두고 있다!
  • 그러나 백엔드끼리 merge할 때, 코드의 일관성이 없고 합쳐진 코드들을 리팩토링할 실력이 안 되었기 때문에 결국에는 지난 주에 받았던 샘플코드를 따라 전부 수정할 수 밖에 없었다 ㅠㅠ
  • 샘플코드는 그저 복붙한 것이어서 기술매니저님이 코드에 대해 질문했을 때 답변 할 수 없었다 ㅠㅠ
  • 팀원들끼리 코드를 어떤 식으로 짤 것인지, 예를들어 예외처리는 각자 할 것인지, 전역으로 할 것인지와 같은 것들은 미리 사전에 정하고 진행하는 것이 좋을 것 같다는 생각이 들었다.

써보고 싶은 코드가 생겼다.

  • 기술매니저님이 잠깐 보여줬던 코드가 있는데 Controller에서 "ResponseEntity" 라는 클래스를 사용하는 것을 보고 찾아보게 되었다. 조만간 관련 내용을 정리해서 올릴 예정이긴 한데, 간단하게 말하면 HTTP 응답 헤더를 포함하여 데이터를 response할 수 있는 객체여서 프론트엔드와 데이터를 주고받을 때 좀 더 섬세하게 컨트롤 할 수 있는 것이라고 할 수 있겠다. 그리고 인증처리를 할 때 Spring Security에서 제공하는 @AuthenticationPricipal이라는 어노테이션도 추천을 받았기에 다음 프로젝트에서는 최소 한 가지는 사용해보고 관련 내용도 정리해두어야 겠다!

이미지 업로드 기능 구현했던 내용

게시글 등록시 이미지 업로드 되게 하기

  • 게시글을 등록할 때 제목을 입력하고 이미지를 등록할 수 있도록 하려고 했다.

  • 문제1 : POSTMAN으로 API 테스트를 할 때, 제목은 content-type이 json이고 이미지는 multipartFile이기 때문에 기존 body에 raw데이터를 json으로 입력하는 것은 불가능했다.

  • 구글링한 결과 PostMapping 어노테이션에 'consumes' 라는 요소로 type 2가지를 명시해주고, 파라미터는 @RequestPart라는 것을 사용하였다.

    // 게시글 등록 
    @PostMapping(value = "/api/auth/post", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) 
    public PostResponseDto createPost(@RequestPart PostRequestDto postRequestDto, @RequestPart(required = false) MultipartFile multipartFile) throws IOException { 
      return postService.createPost(postRequestDto, multipartFile); 
    }

  • API테스트 할 때는 아래와 같이 form-data로 content-type를 나눠서 보낸다.

  • api테스트

  • 문제2 : entity에서 imgUrl을 nullable=true로 설정하였는데도 이미지가 업로드 되지 않으면 파일을 변환할 수 없다는 오류가 생겼다.

  • multipartFile이 null이어도 S3Uploader에서 파일을 변환하여 업로드하는 작업은 진행되는 것 같았고 null인 파일을 변환할 수 없으니 오류가 생긴 것 같았다.

    // 1. MultipartFile을 전달받아 File로 전환한 후에 S3에 업로드
    public String upload(MultipartFile multipartFile, String dirName) throws IOException { 
    if(!multipartFile.isEmpty()) { 
    isImage(multipartFile); 
    } else return null;
    
    File uploadFile = convert(multipartFile) 
          .orElseThrow(() -> new IllegalArgumentException("파일 변환에 실패하였습니다.")); 
    
    return upload(uploadFile, dirName); }

  • multipartFile이 비어있으면 null값을 반환하고 파일을 변환하는 작업은 거치지 않도록 return 하였다.

게시글 삭제시 이미지도 S3에서 삭제되게 하기

  • 문제점 : 게시글을 삭제해도 S3 저장소에는 여전히 이미지 파일이 남아있어서 URL만 알면 접근이 가능했고, 삭제된 게시글의 이미지는 저장소에서 의미없는 용량을 차지하고 있기에 삭제가 필요하였다.

  • S3 저장소의 파일을 삭제할 때 필요한 요소는 객체의 Key값이며 Key값은 객체의 URL에서 버킷주소를 제외한 값이다.(뭐래는 거임)

  • 객체 URL 예시

  • 파일을 업로드하는 과정에서 string fileName이라며 Key값이 만들어지는데 이 값을 어떻게 불러와야할지 고민되었다.

    // 2. S3에 파일 업로드 하기 
    // fileName = S3에 저장되는 파일이름(randomUUID는 파일이 덮어씌워지지 않기 위함) 
    // 1번을 진행하면서 로컬에 생성된 파일을 삭제까지 하는 프로세스 
    private String upload(File uploadFile, String dirName) { 
    String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName(); 
    String uploadImageUrl = putS3(uploadFile, fileName); 
    removeNewFile(uploadFile); 
    return uploadImageUrl; 
    }

  • PostRepository를 필드에 불러온 후에 postRepostory에 fileName을 save하는 방법을 해보았지만, 그러면 정작 PostService에서 title과 imgUrl값이 저장이 되지 않았다.

  • 다음은 게시글 등록처리 하는 과정이다. 이 부분에서 postRepository에 title, imgUrl과 함께 fileName(key값)이 저장이 되어야 삭제를 할 때 키 값을 불러올 수 있을 것 같았다.

    @Transactional 
    public PostResponseDto createPost(PostRequestDto postRequestDto, MultipartFile multipartFile) throws IOException { 
    String imgUrl = s3Uploader.upload(multipartFile, "soso"); 
    String fileName; 
    if(imgUrl == null) { 
      fileName = null; 
    } else { 
      fileName = imgUrl.substring(imgUrl.indexOf("soso")); 
    } 
    
    Post post = Post.builder() 
              .title(postRequestDto.getTitle()) 
              .fileName(fileName) 
              .imgUrl(imgUrl) 
              .build();
    postRepository.save(post); 
    return PostResponseDto.builder() 
              .id(post.getId()) 
             .title(post.getTitle()) 
             .imgUrl(post.getImgUrl()) 
            .createdAt(post.getCreatedAt()) 
             .modifiedAt(post.getModifiedAt()) 
             .build(); 
    }

  • 위에서도 설명했듯이 이미지URL에 bucket주소를 제외해야 하므로, substring을 이용하여 폴더경로 전까지 자르고 fileName이라는 변수에 저장하였다. 이 때, Key값대로 값은 잘 저장이 되었으나 이미지를 업로드하지 않을 경우 imgURL이 null인데 null을 substring할 수가 없으니 오류가 생겼다.

  • 그래서 imgUrl이 null값이면 fileName도 null을 주고, imgUrl이 있을 경우 그 url을 substring해서 key값을 얻을 수 있도록 하였다.

  • 이번 작업을 하면서 가장 고난을 겪었던 두 부분인데, 어떻게 보면 조건문으로 간단하게 끝이났으나 뭔가 코드가 지저분하고 다른 작업물들과 merge가 되면 오류가 생길 수도 있을 것 같다는 생각이 들었다. 이 부분은 다른 방법이 있을 것 같으므로 좀 더 공부한 후에 나은 방법이 있다면 수정을 거쳐야 할 것 같다.

에러처리 내용

  • 수많은 에러를 만났고 어떻게 어떻게 해결했지만 가장 힘들게 했던 에러는 stackoverflow 에러였다. 이 에러와 관련해서는 이미 포스팅을 하였기 때문에 링크로 대체하고자 한다!

Stackoverflow 에러처리 과정 보러가기

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함