[Spring Boot + React] 이미지 포함된 게시판 출력

qufdl·2023년 8월 30일
0

Spring Boot

목록 보기
5/5
post-thumbnail

작성 이유

Spring Boot & React를 이용한 팀 프로젝트를 진행하면서 글을 등록할 때 이미지를 함께 저장하는 기능을 구현했다. 그런데 저장한 이미지를 글과 함께 화면에 출력하는 기능을 구현하면서 React를 담당하는 팀원과 꽤 오랜 시간 삽질을 했다. 삽질한 내용을 기록하려한다.

백엔드로는 이미지 파일을 로컬에 저장하는 방법, DB에 저장하는 방법 두가지를 모두 구현해놨다. DB에 저장할 경우 파일이 많아지면 무리가 갈 것 같아 우선 로컬에 저장하는 방법을 사용했다.

시도1. 이미지를 byte[] 형식으로 Dto에 담아 전달(실패)

처음 구현할 때 프론트 코드에 대해 완전 무지했던 나는 dto에 이미지를 byte[] 코드로 넣어 다른 필드들과 함께 전달

Frontend에 전달한 Dto

public record FindPostResponse(Long id, String title, int heartCnt, int view, byte[] image,
                               LocalDateTime createdAt, LocalDateTime modifiedAt) {

    public static FindPostResponse from(Post post, byte[] image) {
        return new FindPostResponse(
                post.getId(),
                post.getTitle(),
                post.getHearts().size(),
                post.getView(),
                image,
                post.getCreatedAt(),
                post.getModifiedAt()
        );
    }
}

결과

React에서 바이트를 이미지로 바꿀 방법이 있겠지~라는 생각이었는데, 덕분에 프론트 친구를 한참 힘들게했다..미안

시도2. 이미지 파일의 저장 경로를 전달(에러)

DB에 저장된 이미지의 로컬 경로(filePath)를 전달

Not allowed to load local resource 에러 발생

이유 : 보안 문제로 프로젝트 외부에 존재하는 파일 업로드의 경우 접근할 수 없기 때문

에러 해결 방법으로 Tomcat xml 설정을 추가하거나 로컬에 저장하는 방법 등이 있다는데, backend에서 이미지만 출력하는 api가 잘 작동했었기에 분명 다른 방법이 있을 것이라 생각했다.

시도3. 비동기 통신(axios) 사용 (성공!)

비동기 통신 라이브러리인 axios get을 이용해 이미지 조회하도록 구현
이 방법을 사용하면 페이지 변화 없이 해당 api에서 데이터만 가져올 수 있다. 기존 Dto엔 byte[]데이터 대신 fileId를 포함했다.

참고) Post엔티티와 FileData 엔티티는 서로 연관되어있지 않으며, 게시물 등록 시 Post엔티티에 fileId를 저장하도록 구현함

backend(controller)

    @Operation(summary = "게시물 전체 조회(페이징)", description = "제목으로 검색, 추천순/최신순 정렬 가능")
    @GetMapping("/list/paging")
    public ResponseEntity<?> getPostListPaging(
            @RequestParam(value = "title", required = false) String title,
            @RequestParam(value = "sort", defaultValue = "createdAt", required = false) String sort) {
        Pageable pageable = PageRequest.of(0, 20, Sort.by(sort));
        return ResponseEntity.ok(postService.getPostsSearchList(title, pageable));
    }
    
    @Operation(summary = "이미지 다운로드(fileId)", description = "fileId를 통해 이미지를 다운로드하는 방식입니다.")
    @GetMapping("/proudcat/api/image-file-path/{fileId}")
    public ResponseEntity<?> downloadImage(@PathVariable("fileId") Long fileId) throws IOException{
        byte[] downloadImage = fileDataService.downloadImage(fileId);
        return ResponseEntity.status(HttpStatus.OK)
                .contentType(MediaType.valueOf("image/png"))
                .body(downloadImage);
    }

frontend(이미지 데이터 가져오는 코드)

const [imageUrl, setImageUrl] = useState('');

  useEffect(() => {
    axios
      .get(`http://localhost:8080/proudcat/api/image-file-path/${id}`, {
        responseType: 'arraybuffer',
      })
      .then((response) => {
        const url = URL.createObjectURL(
          new Blob([response.data], { type: 'image/png' }),
        );
        setImageUrl(url);
      })
      .catch((error) => {
        console.error(error);
      });
  }, []);

삽질 이유

  • 서로의 코드에 대한 지식이 없음
  • 해당 페이지에 필요한 데이터를 한 api로 전부 전송해야만 한다고 생각
  • 부족한 소통으로 인해 잘못되었음을 늦게 인지

프로젝트 초반엔 나도 js에 대해 완전 무지해서 그냥 데이터만 전달하고 프론트에는 관여할 수 없었다. 그런데 학교 캡스톤 프로젝트를 하면서 반강제적으로 thymeleaf 템플릿을 이용해 프론트를 구현하게 됐고, 그 과정에서 비동기 통신 방법에 대해 알게 되었다. 이를 팀원에게 말했더니 바로 axios 라이브러리로 뚝딱 해결해줬다.

프론트를 구현해야 되는 상황이 왔을 때 처음엔 당황했는데, 이렇게 도움이 되네. 협업을 위해 프론트 공부도 틈틈이 해야할 것 같다

0개의 댓글