이미지를 저장 위치에 대한 고민
프로젝트를 하는 와중에 프로필 이미지 API 구현부분을 맡게 되었다. 이미지 업로드와 저장 관련해서
어디에 해야할지, 이미지를 DB에 저장하는게 맞는건지 외부 server에 저장해야하는건지 조차도 모르는
상태로 시작했다. AWS도 생각을 해보았지만 아직까지 배포 계획이 없는 프로젝트에서 금액부담은 그러했다.
따라서 외부 클라우드 서비스를 찾아보다가 구글 드라이브를 통해 저장을 하기로 마음먹었다.
이미지 업로드 방식의 큰 틀
이미지 업로드부터 url반환까지 과정
1. 프론트엔드에서 이미지 업로드: Node.js를 통해 프론트엔드에서 이미지를 선택하고 업로드한다.
이때 이미지는 URL 형태로 서버로 전송된다.
2. Spring Boot REST API: 스프링부트에서 1번에서 보낸 이미지 url을 받아오고 이것을 REST API를
활용해서 앤드포인트에서 이미지 정보를 처리한다.
3. Google Drive API를 통한 업로드: Spring Boot의 서비스를 활용하여 이미지를 Google Drive로 업로드 한다.
이때 이미지를 업로드 할 폴더를 지정하고 API를 통해 업르도 과정을 처리한다.
4. Google Drive에서 이미지 URL 얻기: 업로드 이후 URL을 서비스단계에서 받아온다.
5. 원래는 URL을 Memeber Repository에 따로 필드를 짜서 저장해야하지만 일단은 그전단계까지만 진행하는 것이므로
Url을 서비스 -> 컨트롤러 -> 프론트로 전달하는 방식으로 했다. 추후 DB에 저장하는 과정도 진행하면 글을 올리겠다.
구글 드라이브 API 사용 토큰 획득
구글 드라이브 API를 사용할려면 토큰이 필요하다. 얻는 방법은 아래와 같다
구글 Colud 이동후 프로젝트 생성
프로젝트 이름을 설정해준다
API를 사용할려는 것이므로 API로 이동해준다
라이브러리로 이동후 Google Drive API를 활성화시켜준다
활성화 버튼까지 누르면 다음과 같이 된다
이후 사용자 인증정보 이동후 OAuth client ID를 생성해줘야한다
이후 생성할때 External(외부) 설정하고 키를 만들면 된다.
그러면 App설정에 관련해서 간단한 정보 제공해주고 설정해주면된다 그 내용은 아래 동영상을 참고 바란다.
https://www.youtube.com/watch?v=1y0-IfRW114&t=261s
위 동영상을 전체 볼필요는 없는것이 위에는 프론트상에서 이미지를 업로드하고 url을 반환하는 형식이다.
따라서 OAuth 설정과 키 토큰얻는 과정만 참고하면 좋을 것 같다.
build.gradle
아래 내용을 추가해줘야한다
//dependencies에 추가
// Google Drive API 종속성 추가(버전 최신으로 설정해줘야하는데 일단은 2.0으로 함 -> 추후변경예정)
implementation 'com.google.api-client:google-api-client:2.0.0'
implementation 'com.google.apis:google-api-services-drive:v3-rev20220815-2.0.0'
//Jackson라이브러리 의존성 추가
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.5'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.5'
// Google HTTP Client Jackson 라이브러리 추가
implementation group: 'com.google.http-client', name: 'google-http-client-jackson2', version: '1.41.0'
Properties
이거는 추가할 필요는 없으나 그래도 용량 제한을 두는것이 좋을것 같아서 추가했다.
하지만 당장은 이부분에 대해서 깊게 공부한것이 아니어서 추후 변경할 예정이다.
변경 이유는 다음과 같이 작성하면 설정해놓은 용량을 초과하면 업로드를 막는식이다. 원하는 방식은
용량이 크면 용량을 줄여서 업로드하게끔 하는것이기에 추가로 공부해보고 변경할 예정이다.
# 파일 업로드 및 다운로드 관련 설정 - 추후에 용량 정정필요
spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=2KB
spring.servlet.multipart.max-file-size=200MB
spring.servlet.multipart.max-request-size=215MB
Google Drive Config
구글 드라이브 API사용을 위한 토큰인증 관련된 내용이다.
클라이언트 id, 암호, 리프래쉬토큰을 이용해 인증과정을 거치고 이후 API를 사용할 수 있다.
package Alchole_free.Cockpybara.config;
import com.google.api.client.googleapis.auth.oauth2.*;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Arrays;
@Configuration
public class GoogleDriveConfig {
private final String CLIENT_ID = "클라이언트 아이디";
private final String CLIENT_SECRET = "암호";
private final String REFRESH_TOKEN = "클라이언트 토큰과 암호를 통해 얻은 리프래쉬 토큰";
private final String APPLICATION_NAME = "애플리케이션 이름";
@Bean
public Drive driveService() throws IOException, GeneralSecurityException {
// Google Drive 인증 및 설정
JsonFactory jsonFactory = new JacksonFactory();
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
// GoogleCredential 객체 생성
GoogleCredential credential = authorize(jsonFactory, httpTransport);
// Drive 객체 생성
return new Drive.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
private GoogleCredential authorize(JsonFactory jsonFactory, HttpTransport httpTransport) throws IOException {
GoogleClientSecrets clientSecrets = loadClientSecrets(jsonFactory);
// OAuth 2.0 인증 코드 플로우 설정
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, clientSecrets,
Arrays.asList("https://www.googleapis.com/auth/drive"))
.build();
// GoogleCredential 객체 생성
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build();
// 이미 얻어온 리프래시 토큰 설정
credential.setRefreshToken(REFRESH_TOKEN);
return credential;
}
private GoogleClientSecrets loadClientSecrets(JsonFactory jsonFactory) throws IOException {
InputStream inputStream = getClass().getResourceAsStream("/client_secrets.json");
return GoogleClientSecrets.load(jsonFactory, new InputStreamReader(inputStream));
}
}
Controller
프론트에서 URL형식으로 보낸 이미지를 스프링부트의 Multipartfile을 이용하여 Controller에서 받아온다.
이후 서비스단계로 넘겨준다
package Alchole_free.Cockpybara.image;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api")
public class ImageController {
private final ImageService imageService;
@Autowired
public ImageController(ImageService imageService) {
this.imageService = imageService;
}
@PostMapping("/image-upload")
public ResponseEntity<String> uploadImage(@RequestPart MultipartFile file) {
//이미지 업로드하고 url받기
String imageUrl = imageService.uploadImage(file);
return ResponseEntity.ok(imageUrl);
}
}
Service
서비스 단계에서는
파일 메타데이터
간략하게 설명하면 파일을 식별하고, 관리, 보안 설정, 검색과 분석을 도와주는 역할을 한다.
package Alchole_free.Cockpybara.image;
import com.google.api.client.http.InputStreamContent;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Collections;
@Service
public class ImageService {
private final Drive driveService;
@Autowired
public ImageService(Drive driveService) {
this.driveService = driveService;
}
public String uploadImage(MultipartFile file) {
try {
// 파일 메타데이터 생성
File fileMetadata = new File();
fileMetadata.setName(file.getOriginalFilename());
fileMetadata.setParents(Collections.singletonList("your Google Drive Folder")); // 부모 폴더 ID 설정
// 파일 컨텐츠 생성
InputStreamContent mediaContent = new InputStreamContent(file.getContentType(), file.getInputStream());
// 파일 업로드
File uploadedFile = driveService.files().create(fileMetadata, mediaContent)
.setFields("id, webViewLink")
.execute();
// 업로드한 파일의 웹 링크 반환
String uploadedImageUrl = uploadedFile.getWebViewLink();
return uploadedImageUrl;
} catch (IOException e) {
// 파일 업로드 중 IOException 발생 시 처리
e.printStackTrace();
return "Error uploading image: " + e.getMessage();
} catch (Exception e) {
// 기타 예외 발생 시 처리
e.printStackTrace();
return "Error uploading image: An unexpected error occurred.";
}
}
}
Postman을 이용한 테스트
테스트에 앞서 post 설정후 Body에서 라이브러리에 있는 사진파일을 선택해준다
가끔 postman 관련해서 Couldn't upload file Make sure that Postman can read files inside the working directory. 오류가 뜰때가 있는데 관련된 내용 해결법은 링크를 남기겠다
https://developerjisu.tistory.com/42
이후 프로그램 실행을 해준다
Send 버튼을 눌러준다
그러면 아래 이미지의 url이 반환되는것을 볼 수있다.
이제는 구글 드라이브에 들어가서 이미지가 잘 들어왔는지 보겠다.
이미지가 잘 저장된 모습을 볼 수있다
이상으로 이미지 업로드 관련된 내용을 마치겠다
추후 repository작업을 하게되면 내용 덧붙이도록 하겠다
'스프링' 카테고리의 다른 글
[AI] 이미지 생성시 DALLE에서 Stable Diffusion으로 AI모델 변경 with SpringAI (2) | 2025.02.02 |
---|---|
AWS S3와 IAM 생성: 스프링과의 연동 방법 및 이미지 저장 (0) | 2024.03.20 |
[Spring] Validation 종류 및 적용 (0) | 2024.03.15 |
[스프링] 스프링 3.1이후에 디버그 로그가 안뜰경우, 로그레벨 조정 방법 (0) | 2024.01.29 |
[스프링] 혼자 구현하는 웹 서비스1: 프로젝트 세팅 (0) | 2024.01.08 |