전체 보기
📁

Springboot에서 GCS로 이미지 파일 업로드 총정리(multipart/form-data)(Google Cloud Storage)(GCP)

작성일자
2023/02/20
태그
INFRA
HIGHLIGHT
프로젝트
BeachCombine
책 종류
1 more property

1. 서론

프로젝트에서 이미지는 어떻게 저장되는 걸까요? 보통은 AWS S3나 Google Cloud Storage와 같은 Storage에 저장합니다. 이번 포스팅에선 이미지를 Google Cloud Storage에 저장하고, 꺼내 쓰는 법을 알려드리겠습니다.
저는 클라이언트에서 보낸 파일을 바로 Storage에 올리는 방식을 설명할 것입니다. Google Cloud Storage에 이미지 파일을 업로드하는 한글로 된 포스팅이 기존에 세 개밖에 없었고, 이마저도 로컬에 있는 이미지 파일을 업로드하는 내용이었습니다. 클라이언트로부터 받은 이미지 파일을 바로 업로드하는 포스팅은 한 개도 없어서 삽질을 좀 했습니다,,ㅎㅎ 다른 분들은 삽질하지 않도록 제가 한 방식을 최대한 상세하게 정리해보겠습니다
방식의 기본 원리는 AWS S3에 업로드하는 원리와 동일합니다. 다만, 설정 관련한 부분은 상이해 Google Cloud Storage를 쓰실 분들은 제 포스팅을 보시면 도움이 될 것입니다.
파일을 AWS S3에 업로드하는 원리가 궁금하시다면? 토글을 내려봐요
전체 코드는 아래 링크에서 확인 가능합니다.
33
pull

2. Cloud Storage란?

Google Cloud에 어떤 형식의 파일이든 저장하게 해주는 서비스입니다. 정확히는 버킷이라는 컨테이너에 파일을 저장합니다. AWS S3 버킷과 유사합니다.
AWS vs GCP
AWS
GCP
Comment
S3
Cloud Storage
스토리지
S3의 버킷
Cloud Storage의 버킷
하나의 버킷에 하나의 객체(파일)을 저장함

3. Cloud Storage Bucket 생성

[1] 콘솔로 이동

[2] Cloud Storage - 버킷으로 이동

[3] 버킷 만들기

1.
상단에 만들기 버튼 클릭
2.
저는 아래와 같이 입력했습니다. (아래 노란 체크한 부분 해제하는 걸 꼭 해주세요!)
3.
버킷에 직접 파일 업로드 해보기
테스트로 버킷에 파일을 직접 업로드해보아요. 업로드한 파일 이름(고양이.JPG)를 클릭하시면 인증된 URL이란 게 뜹니다. 이 URL로 접속해보시면 아래처럼 업로드했던 사진이 뜹니다!
다만, 공개 액세스가 아니기에 다른 구글 계정에선 해당 URL로 업로드한 사진을 볼 수 없습니다. 저흰 클라이언트분이 이 URL만 가지고 화면단에 이미지를 띄어줄 수 있게하기 위해 공개 액세스를 적용해주겠습니다.
4.
공개 액세스 적용
a.
버킷 선택 후, 권한 탭으로 이동
b.
액세스 권한 부여
c.
버킷 새로고침 후 확인
이제 해당 Bucket의 모든 객체들은 모든 사용자들에게 공개된 상태로 변경되었습니다. 실제로, URL을 복사하셔서 다른 구글 계정이나 혹은 시크릿 모드에서 URL 접속하시면 업로드한 사진을 보실 수 있습니다.
생각해볼 것! 백엔드가 이 URL을 프론트에게 넘겨주면, 프론트는 이 URL을 <img src={image_url} alt=""/>와 같이 링크로 넣어주어 화면에 띄웁니다.
URL은 https://storage.googleapis.com/{Bucket Name}/{파일경로}/{파일명} 꼴입니다. 이때, 주의할 점이 있습니다. 파일명을 사용자가 넣은 파일 이름을 그대로 가져다 쓴다면 중복이 충분히 생길 수 있겠죠.
ex. https://storage.googleapis.com/beach-combine-bucket/고양이
Storage내에서 이름이 동일한 파일은 덮어쓰기가 되거나 저장되지 않기 때문에 파일명을 중복되지 않게 만들어줘야 합니다. 예를 들면, 날짜+파일명을 쓰거나, UUID를 쓰는 방법이 있습니다.
이번 포스팅에선 UUID를 써보겠습니다! UUID를 쓰면 URL이 아래와 같이 저장됩니다.
ex. https://storage.googleapis.com/beach-combine-bucket/9b147ba6-590c-4a13-few3-cd12c4df56c1
참고로, AWS S3에선 아래와 같이 저장됩니다.
ex. https://beach-combine-bucket.s3.ap-northeast-2.amazonaws.com/9b147ba6-590c-4a13-few3-cd12c4df56c1.jpeg

[4] 접근 권한 허용, key 파일 생성

위에서 공개 액세스를 적용했는데 접근 권한 허용을 왜 또 하는지 궁금하실 수 있어요. 허나, 이 과정은 key를 만들어, 스프링부트에서 key를 이용해 storage에 접근할 수 있도록 해주니 따라해 주세요!
a.
IAM 및 관리자 - 서비스 계정으로 이동
b.
서비스 계정 만들기 클릭
이름은 springboot-storage-access로 하고 아래와 같이 입력해 만들어줬습니다. + 추가로! 저장소 개체 생성자도 넣어주세요!!! 저는 뒤늦게 넣었지만,, 여러분은 미리 넣으셔서 에러 안나시길,,
c.
키 만들기
만든 서비스 계정을 클릭하고서, 키 탭으로 이동해 키 추가를 클릭합니다.
만든 json 키 파일은 컴퓨터에 저장해둡니다. 나중에 프로젝트의 resources 폴더 안으로 옮길거에요! 위치 기억해두기!
아래는 제가 받은 key파일 내부이고, 제 key 파일 이름은 beach-combine-377 어쩌구 입니다.

4. 스프링 프로젝트에서 클라한테 받은 이미지 업로드

[1] build.gradle에 의존성 추가

implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter', version: '1.2.5.RELEASE' implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-storage', version: '1.2.5.RELEASE'
Java
복사
오류 났던 코드

[2] application.yml에 key 등록해 storage 변수에 의존성 주입

위에서 만든 json key file을 resources 폴더에 넣고, application.yml에 아래 코드를 추가해줍니다.
spring: cloud: gcp: storage: credentials: location: classpath:{위에서 만든 key 파일 이름}.json project-id: {key 파일 안에 있는 project_id 값} bucket: {버킷 이름}
Java
복사
key 파일에는 type, project_id, private_key_id, private_key 등 storage를 사용하는데 필요한 내용이 저장되어있습니다. 따라서 application.yml에 키 파일의 경로를 적어주면 스프링 부트는 키 파일의 내용을 바탕으로 stroage 변수에 자동으로 의존성을 부여합니다.
주의할 점은 키 파일의 경로 외에도 project-id를 꼭! 넣어주셔야 한다는 것입니다. 넣지 않으면 의존성이 제대로 부여되지 않습니다. 버킷 이름은 Cloud Storage 콘솔 들어가시면 나오는 버킷 이름입니다.
project-id 안 넣어서 떴던 에러,,,
혹시 storage에 의존성이 부여되지 않는다는 에러가 뜬다면? 토글을 열면 해결책이 있습니다.

[3] 이미지 업로드 코드

회원 정보를 수정하는 API에서 이미지를 업로드하는 부분이 있어 해당 코드로 보여드리겠습니다. 최대한 쓸모 없는 코드는 빼고 이미지 업로드 관련한 부분만 담아 보여드리겠습니다.
Request dto
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder public class MemberUpdateRequest { private String nickname; private MultipartFile image; //!!!!!!!!!이미지 업로드 관련 부분 }
Java
복사
후에 아래와 같이 request 요청을 보낼 것입니다. 파일을 올릴 필드는 MultipartFile 타입으로 선언해줍니다.
Controller
@PatchMapping("") public ResponseEntity<Void> updateMemberInfo(MemberUpdateRequest dto) throws IOException { memberService.updateMemberInfo(dto); return new ResponseEntity(HttpStatus.OK); }
Java
복사
주의할 점은 @Requestbody 어노테이션을 붙이지 않는단 점입니다. 붙이면 토글 속 에러가 납니다.
또 다른 방법으론 아래와 같이 코드를 짜실 수도 있습니다. 위에 코드와 아래 코드는 둘 다 똑같은 의미입니다. dto를 안 쓰고 이미지만 인자로 받고싶으실 땐 아래 방법에서 dto를 빼서 사용하시면 될 거 같습니다.
Service
@Value("${spring.cloud.gcp.storage.bucket}") // application.yml에 써둔 bucket 이름 private String bucketName; // 회원 정보 수정 public void updateMemberInfo(MemberUpdateRequest dto) throws IOException { Member findMember = memberRepository.findById(1) // 아이디는 spring security를 이용해 따로 받지만, 예시코드에선 1로 썼습니다. .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_MEMBER)); // !!!!!!!!!!!이미지 업로드 관련 부분!!!!!!!!!!!!!!! String uuid = UUID.randomUUID().toString(); // Google Cloud Storage에 저장될 파일 이름 String ext = dto.getImage().getContentType(); // 파일의 형식 ex) JPG // Cloud에 이미지 업로드 BlobInfo blobInfo = storage.create( BlobInfo.newBuilder(bucketName, uuid) .setContentType(ext) .build(), dto.getImage().getInputStream() ); findMember.updateMemberInfo(dto, uuid); // DB에 반영 }
Java
복사
버킷 이름 안넣어서 떴던 에러,,,
Entity
public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") private Long id; private String nickname; private String image; // Google Cloud Storage에 저장된 이미지 파일 이름 public void updateMemberInfo(MemberUpdateRequest dto, String imageUrl) { this.nickname = dto.getNickname(); this.image = imageUrl; } }
Java
복사
DB에는 이미지 파일 자체를 저장하는게 아니고 이미지가 업로드된 URL(UUID 값만 저장하셔도 되고, 전체 URL을 저장하셔도 좋아요)을 저장할 것이기에 String 타입으로 지정해주시면 됩니다.

[4] 이미지 업로드 성공!

URL 복사해서 접속해보세요!

5. 결론 + 클라한테 저장한 이미지 다시 보내줄 땐?

이미지 업로드 성공하셨길 바랍니다!
클라가 저장했던 이미지를 다시 보내 달라할 땐 사실 굉장히 간단하고 쉽습니다!
저희가 DB에 String 타입으로 저장해둔 UUID값을 보내주기만 하면 된답니당 ㅎㅎ
더 정확히는, String 타입으로 https://storage.googleapis.com/버킷이름/UUID값 을 보내주심 됩니다!
해당 URL을 들어가면 이전에 storage에 저장해둔 이미지가 뜨니깐요!
참고
4) ChatGPT
Q) 클라이언트에서 multipart/form-data타입으로 보낸 파일을 google cloud storage에 올리는 코드를 스프링부트, JPA 이용해서 짜줘
A) 컨트롤러에서 storage.create 부분 참고함