지난 포스팅에서는 Spring Boot와 AWS S3를 연동하는 방법에 대해 다루어 보았습니다. 연동을 했다면 S3를 이용해 파일을 업로드할 수 있어야 할 것입니다. 또한 삭제가 필요한 상황이면, API를 호출해서 삭제도 할 수 있어야 합니다. 이번 포스팅에선 그 과정에 대해 알아보려 합니다.
오늘 소개할 코드는 아래의 깃허브 링크를 통해 확인하실 수 있습니다. 제 벨로그의 시리즈 중 하나인 따라하면서 배우는 JPA에서 사용하는 코드입니다.
>> 깃허브 링크
① AWS S3 서비스에 들어간다.
② 좌측 메뉴의 버킷을 클릭한 후 저번에 생성한 버킷을 클릭한다.
③ 권한 탭을 누르고 스크롤을 아래로 내리면 객체 소유권이 보일 것이다. 우측의 편집 버튼을 클릭한다.
④ 아래와 같이 설정을 변경한다.
ACL(Access Control List)을 활성화함으로써 AWS S3 버킷의 객체에 대한 액세스 권한을 제어할 수 있다. S3 버킷은 기본적으로 개인 또는 팀의 액세스만 허용하도록 구성되어 있다. 따라서 ACL을 활성화하면, 적절한 액세스 제어를 위해 권한을 설정해야 한다. 저번 포스팅에서 설정한 것이 이 과정에 해당한다. 이로써, 버킷 설정이 완료된다.
이번 포스팅부터는 postman에 이미지를 넣을 것이므로 관련된 설정을 먼저 진행하려 한다.
① postman의 우상단에 설정 버튼 > Settings > General > Loacation에 들어간다.
② choose 버튼을 이용해 사용할 이미지가 있는 위치를 선택하면 된다. 위 예시에서는 바탕화면을 지정하고 있다.
③ S3에 업로드하려는 image 파일을 바탕화면에 둔 상태에서 API를 실행한다.
이로써 postman 설정까지 완료된다. 이제 코드만 작성하면 S3에 파일을 업로드할 수 있다.
이번 포스팅에서는 S3에 이미지를 업로드하기 위해 알아야 하는 최소한의 기능만 설명만 하고, 나머지 설명은 아래의 링크에서 이어서 진행할 예정이니 참고 바란다.
>> 따라하면서 배우는 JPA
게시판이라면, 글뿐 아니라 사진도 게시할 수 있어야 하는만큼 Board 클래스에 사진을 게시할 수 있는 기능이 추가되었다.
게시글과 사진의 매핑관계는 일대다이다. 즉, 하나의 게시글에는 여러 사진이 게시될 수 있다는 의미이다. 일대다관계에서 주인은 누구인가? 앞서 이야기한 바 있듯 일대다관계에서는 다가 주인이다. 여기서는 게시 사진이 주인이 되고, 게시글은 이에 mappedBy된다. 그리고 Board 클래스에서는 PostPhoto를 리스트 형태로 관리해야 한다.
① PostPhoto 클래스는 Board 테이블의 주인이다.
② createBoard()
① 게시된 사진들을 List 형태로 관리하기 위해 photoList에 add하고 있다.
② 앞서 설명한 createBoard() 메서드로 게시 사진에 매핑될 게시글을 set하고 있다.
① HTTP 요청의 일부를 처리하기 위해 사용되는 어노테이션이다.
② @RequestPart는 @RequestParam과 비슷한 역할을 수행하지만, @RequestParam은 주로 application/x-www-form-urlencoded 형식의 요청 파라미터를 처리하는 데 사용되는 반면, @RequestPart는 multipart/form-data 형식의 요청 데이터를 처리하는 데 사용된다.
③ 파일 업로드와 관련된 작업을 수행하는데 주로 사용된다.
④ value 속성
⑤ require 속성
아래의 사진을 보면 @RequestPart를 이해하는 데에 도움이 될 것이다.
① 파일 업로드를 처리하기 위해 제공되는 인터페이스이다.
② 업로드된 파일의 메타데이터와 파일 데이터를 제공하며, 파일의 원본 이름, 크기, MIME 유형 등의 정보를 얻을 수 있다.
※ 메타데이터
메타데이터는 데이터의 특성, 속성 또는 설명을 제공하는 정보이다. 특히 파일에서의 메타데이터는 파일에 대한 추가적인 정보를 의미한다. 예를 들면 파일의 이름, 크기, 생성 일자, 수정 일자, 파일 형식 등이 포함될 수 있다. 이러한 정보는 파일을 식별 및 분류하고, 파일에 대한 추가적인 작업을 수행하는 데에 유용하다.
※ MIME
Multipurpose Internet Mail Extensions의 약자로, 인터넷에서 다양한 종류의 데이터를 식별하는 데 사용되는 표준화된 방법이다. MIME은 데이터의 형식이나 유형을 나타내기 위해 사용되며, 주로 파일의 확장자에 기반하여 식별한다.
MIME 유형은 데이터의 특성과 형식을 기술하는 문자열로 구성되며, 주 타입과 서브타입으로 구분된다. 예를 들어 "text/plain"은 일반텍스트 데이터를 나타내고, "image/jpeg"는 JPEG 이미지 파일을 나타낸다.
③ MultipartFile은 파일 데이터를 읽고 저장할 수 있는 다양한 메서드를 제공한다.
① Board 객체의 빌더를 생성한다. 빌더는 객체 생성과 속성 설정을 동시에 처리할 수 있는 방법이다.
② .photoList(new ArrayList<>())
① @Data
② List<GetS3Res>
① for each 람다식
② createFileName()
UUID란 Universally Unique IDentifier의 줄임말로, 네트워크 상에서 고유성이 보장되는 id를 만들기 위한 표준 규약이다.
UUID.randomUUID().toString()은 랜덤한 고유 식별자(UUID)를 생성하고, toString() 메서드를 통해 문자열 형태로 변환한다.
UUID는 일반적으로 32자의 16진수로 표현된다.
getFileExtension()
conacat 메서드로 UUID 문자열과 확장자를 결합한다.
완전히 동일한 파일을 업로드하더라도 fileName은 unique하게 설정된다.
③ objectMetadata
④ file.getInputStream()
⑤ putObject()
⑥ IOException
⑦ fileList.add
① for each문
② savePostPhoto()
Http 요청 헤더에서 access token을 가져와 parsing한 후 멤버의 ID를 추출하여 반환하는 메서드이다. 게시글 삭제 요청에 멤버의 ID 값이 필요한 이유는 아래에서 자세히 설명하겠다.
writer는 삭제하려는 게시글의 작성자를 의미하고, visitor는 게시글을 방문한 사람(삭제 요청을 보낸 사람)을 의미한다. 만약, 게시글을 작성하지 않은 사람이 게시글에 대한 삭제 요청을 보냈다면, 이 요청은 처리되지 않아야 한다.
따라서 access token에서 파싱한 멤버(visitor)의 ID와 게시글 작성자(writer)의 ID를 비교하여 같은 경우에 대해서만 요청을 처리한다. 이것이 1번에서 member의 ID 값을 추출한 이유이다. 이로써 게시글 작성자가 삭제를 요청한 경우에만 게시글 삭제가 진행될 수 있다.
게시글의 ID를 입력하면 해당하는 게시글에 게시된 사진을 모두 select하여 반환하는 메서드이다. 반환된 사진은 allByBoard라는 리스트에 추가된다.
게시 사진을 받아 for each 문으로 리스트를 순회하며 파일의 이름으로 삭제를 진행한다.
deleteObject() 메서드는 DeleteObjectRequest 타입의 객체를 입력 파라미터로 받는데, DeleteObjectRequest는 버킷의 이름과 key를 포함하고 있다. 여기서 key는 파일의 이름으로 고유 식별자이기 때문에, 다른 게시글에 존재하는 동일한 사진은 지워지지 않는다. 이로써 삭제하려는 게시글에 게시된 사진이 모두 S3에서 제거된다.
findAllByBoardId와 입력 파라미터도 갖고 where 조건도 같지만, id를 반환한다는 점에서 차이가 있다. 즉, 삭제하려는 게시글에 게시된 사진들의 ID를 select하여 반환하는 메서드이다. 반환된 ID는 ids라는 리스트에 저장된다.
게시 사진의 ID를 이용해 Repository에서 삭제하는 메서드이다. 이로써 PostPhotoRepository에서 사진을 삭제할 수 있다. 물론 아래의 JPQL 쿼리를 이용하여 한번에 PostPhoto를 삭제하는 것도 가능하다.
마지막으로 Board Repository에서 게시글을 삭제하면 모든 삭제 과정이 완료된다. 정리하면, 하나의 게시글을 삭제하기 위해선 3번의 삭제 과정이 필요하다.
이번 포스팅을 통해 S3에 파일을 업로드하고, 삭제하는 방법을 알아보았다. 구현한 API에 대한 테스트 방법과 결과에 대해서는 따라하면서 배우는 JPA 시리즈를 참고하기 바란다.