<요약>
- 목적
- GCS로 이미지 저장, 삭제, 수정 구현
- 설명
- GCS는 Storage 객체를 통해 이미지 저장소(bucket)에 접근 가능
- Storage를 통해 이미지 저장 및 삭제 가능(수정은 불가)
- 보완 계획
- GCS 이미지 등록을 비동기적으로 수행
- Storage 객체를 비동기적 또는 멀티 스레드 방식으로 해결 예정
- (현재는 ADMIN 관리자만 상품 등록이 가능하므로, GCS를 스프링 빈으로 선언해서 싱글톤 객체로 쓰고 있음)
- GCS 이미지 등록을 비동기적으로 수행
QnA)
1. GCS 용어
- 용어
https://techblog-history-younghunjo1.tistory.com/27
- Bucket
- 데이터 저장소
- Bucket안에 Bucket이 있을 수 없음
- 각 Bucket 안에 각 데이터(이미지)를 저장 가능
- Object
- Bucket 안에 있는 각 데이터
- 구성 요소
- 객체 데이터
- 실제 데이터 내용
- ex) 이미지
- 객체 메타 데이터
- 객체 데이터 부연 설명
- key-value 형태로 객체 데이터를 설명함
- ex)
- 객체 데이터
{
"name": "images/cat.jpg",
"contentType": "image/jpeg",
...
}
2. GCS 업로드 권한
- 요약
- 목적
- GCS에 업로드한 이미지를 인터넷의 모든 사람들이 볼 수 있도록 한다.
- 방법
- GCS 버킷 사용 시, 접근이 가능한 사용자를 지정할 수 있다.
- 목적
- 적용
- GCS → 버킷 → 권한 → 액세스 권한 부여로 이동
- 해당 액세스 권한 부여에서 구성원 및 역할 지정이 가능하다
- 구성원
- https://cloud.google.com/iam/docs/overview?hl=ko#concepts_related_identity
- 권한을 부여할 대상을 선택한다.
- 본인은 allUsers를 선택했다.
- allUsers : 인터넷 상의 모든 사람 접근 가능
- 역할 지정
- https://cloud.google.com/iam/docs/understanding-roles
- 버킷의 접근 권한을 지정한다.
- 본인은 “저장소 개체 뷰어”를 선택했다.
- 저장소 개체 뷰어
- 객체, 객체 메타데이터 읽기 가능
- ⇒ 이미지 읽기 가능
- 저장소 개체 뷰어 ACL을 제외한 객체와 객체의 메타데이터를 볼 수 있는 액세스 권한을 부여합니다. 버킷의 객체를 나열할 수도 있습니다
- 저장소 개체 뷰어
3. Bucket 사용 권한 획득
https://choo.oopy.io/35bffd94-7a41-4cfa-812c-b8aaf148604a
- 목적
- 스프링으로 Bucket에 이미지를 등록, 수정, 삭제하는 것
- 코드 상에서 Bucket을 수정할 수 있는 권한을 획득한다.
- 스프링으로 Bucket에 이미지를 등록, 수정, 삭제하는 것
- 방법
- 액세스 권한 획득
- 원격으로 Bucket에 접속하기 위해, 접속 권한을 획득한다.
- IAM 및 관리자 → 서비스 계정 → 서비스 계정 만들기
- 액세스 권한 획득
- 접속 권한 등록
- 사용 권한
- 저장소 개체 관리자
- Bucket 안에 있는 각 객체에 대한 CRUD 권한 획득
- 저장소 관리자
- Bucket 제어 권한 획득(=Bucket에 접속 권한 획득)
- 저장소 개체 관리자
- 사용 권한
- 키 획득
- 위에서 설정한 권한을 가진 키를 획득
- (본인은 JSON으로 획득)
4. Spring에서 Bucket 수정
- 목적
- Spring에서 이미지 업로드
- gradle.yml 추가
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'
2. 사용
- 절차
- Storage 획득
- Storage : GCS 전체를 관할하는 객체 → 즉, 각 Bucket에 접속 가능
- Storage를 사용하기 위해서는 2가지 입력 필요
- GCS 액세스 권한(위에서 가져온 JSON 키 활용)
- project-id
- 구글 클라우드에서 현재 사용하고 있는 프로젝트 id
- (1. GCS 액세스 권한 파일에 project-id 란으로 확인 가능)
- ⇒ 해당 구글 클라우드 프로젝트에 GCS 사용 권한을 획득한 Storage 객체를 사용가능한 것
- Storage 획득
//액세스 권한 키가 있는 절대 위치
String location = C:\Windows\System32\MyProject\sportsshop-44.json;
String projectId = google-cloud-project-id
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(location));
Storage storage = StorageOptions.newBuilder()
.setCredentials(credentials)
.setProjectId(projectId)
.build()
.getService();
2. Storage 사용
- bucketName : 사용할 Bucket 이름
- BlobId : 버킷의 객체 id
- BlobInfo : BlobId 객체의 메타 데이터
String bucketName = google-cloud-gcs-bucket-name;
//1번에서 언급된 대로 storage 생성
Storage storage = ...;
//저장할 이미지 파일
MultipartFile image
//버킷에 저장할 이미지 이름
String imageName = bucket-object-name;
String ext = image.getContentType(); // 파일의 형식 ex) JPG
// 객체 id 생성(= imageName)
BlobId blobId = BlobId.of(bucketName, imageName);
// 객체 메타 데이터
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType(ext)
.build();
//객체 쓰기 객체
WriteChannel writer = storage.writer(blobInfo)
//image 쓰기
byte[] imageData = image.getBytes();
writer.write(ByteBuffer.wrap(imageData));
//=> bucketName에 image 쓰기 완료
5. 이미지 업로드 예시
https://velog.io/@julia2039/SpringBoot-프로젝트에서-GCS로-이미지-업로드-Google-Cloud-StorageGCP
- 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'
yml
spring:
cloud:
gcp:
storage:
credentials:
location: C:\Windows\System32\MyProject\~~~~.json
project-id: ~~~~
bucket: ~~~~
- 저장
- 사진은 MultipartFile 타입
- blocking 방식으로 이미지 업로드가 진행됨
- 1번에 1번의 요청 처리 및 그 외의 요청은 대기 상태에 빠짐
public class ImageUpLoad {
@Value("${spring.cloud.gcp.storage.bucket}") // application.yml에 써둔 bucket 이름
private String bucketName;
@Value("${spring.cloud.gcp.storage.project-id}")
String projectId;
@Value("${spring.cloud.gcp.storage.credentials.location}")
String location;
// 회원 정보 수정
public void updateMemberInfo(MultipartFile file) throws IOException {
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(location));
Storage storage = StorageOptions.newBuilder()
.setCredentials(credentials)
.setProjectId(projectId)
.build()
.getService();
String uuid = UUID.randomUUID().toString(); // Google Cloud Storage 에 저장될 파일 이름
String ext = file.getContentType(); // 파일의 형식 ex) JPG
// Cloud에 이미지 업로드
BlobId blobId = BlobId.of(bucketName, uuid);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType(ext)
.build();
try (WriteChannel writer = storage.writer(blobInfo)) {
byte[] imageData = file.getBytes();
writer.write(ByteBuffer.wrap(imageData));
} catch (Exception ex) {
// 예외 처리 코드
ex.printStackTrace();
}
}
}
- 이미지 삭제
- 이미지는 id값으로 삭제 가능
- 해당 id값은 BlobId 타입
- BlobId는 bucketName + uuid로 이루어짐
- 따라서, imageUrl에 있는 bucketName과 uuid로 삭제 가능
public void deleteImage(String imageUrl){
Storage storage = createStorage();
String uuid = parseUuid(imageUrl);
BlobId blobId = BlobId.of(bucketName, uuid);
if(storage == null){
log.info("Storage 생성 실패");
return;
}
if(!storage.delete(blobId)){
log.info("GCS 이미지 삭제 실패");
return;
}
log.info("GCS 이미지 삭제 성공");
}
private String parseUuid(String imageUrl){
int lastSlashIndex = imageUrl.lastIndexOf('/');
if (lastSlashIndex == -1) {
throw new IllegalArgumentException("올바르지 않은 URL 형식입니다.");
}
return imageUrl.substring(lastSlashIndex + 1);
}
private Storage createStorage(){
GoogleCredentials credentials = null;
try{
credentials = GoogleCredentials.fromStream(new FileInputStream(location));
} catch (Exception e){
log.error("이미지 인증 정보 생성 실패");
return null;
}
return StorageOptions.newBuilder()
.setCredentials(credentials)
.setProjectId(projectId)
.build()
.getService();
}
- 이미지 수정
- GCS는 이미지 덮어쓰기 기능이 존재함
- 덮어쓰기는 기존 URL을 유지하되, 새로운 이미지로 대체되는 것
- (원래 이미지로 복구도 가능하다고 하는 데 더 디테일한 조사 필요)
public String overWriteImage(MultipartFile newImage, String originalImageUrl){
Storage storage = createStorage();
if(storage == null){
log.info("Storage 생성 실패");
return null;
}
String uuid = parseUuid(originalImageUrl);
String ext = newImage.getContentType();
// Gcs 이미지 업로드
BlobId blobId = BlobId.of(bucketName, uuid);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType(ext)
.build();
try (WriteChannel writer = storage.writer(blobInfo)) {
byte[] imageData = newImage.getBytes();
writer.write(ByteBuffer.wrap(imageData));
} catch (Exception e) {
log.error("이미지 전송 실패");
return null;
}
return originalImageUrl;
}
※ 주의
- 이미지의 public, private의 주소가 다름
- allUser해서 public으로 이미지 저장 시, domain 수정이 필요함
- private
- public
'스프링' 카테고리의 다른 글
테스트 코드 (0) | 2025.02.05 |
---|---|
JWT_사용자 정의 로그인 (0) | 2025.01.31 |