일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- perspective api
- 무한 스크롤
- 검색 기능
- 카카오 간편 로그인
- CI/CD
- google font icon
- firebase storage
- 웹서비스 배포
- 웹 개발
- 초보 개발자
- 파일 업로드
- Stomp
- CloudType
- 욕설 및 비속어 필터링
- 크롬 확장 프로그램
- Github Actions
- 웹개발
- 초성 검색
- 1인 개발
- 웹소켓
- 구글 간편 로그인
- 네이버 간편 로그인
- Today
- Total
J2ong 님의 블로그
Firebase Storage를 활용한 파일 업로드 구현 방법 본문
안녕하세요!
이번 글은 이전 게시글인 파일 업로드 구현 방식에 이어서, Firebase Storage를 활용해 배포 환경에서도 안정적으로 동작하는 파일 업로드 방식을 소개합니다.
파일 업로드 통합 구현 방식은 이전 게시물을 참고해주세요.
2025.04.21 - [개인 프로젝트] - Spring + React 파일 업로드 통합 구현 (korean-romanizer 라이브러리를 활용한 한글 파일명 오류 해결)
Spring + React 파일 업로드 통합 구현 (korean-romanizer 라이브러리를 활용한 한글 파일명 오류 해결)
안녕하세요!이번 글에서는 Spring Boot와 React 환경에서 파일 업로드 기능을 구현하는 방법을 공유합니다. 특히 한글 파일명을 업로드할 때 생길 수 있는 문제를 해결하는 방법도 함께 다룹니다.📌
talkhub.co.kr
📌 Firebase Storage를 사용한 이유
기존에는 파일을 file.path 기반으로 로컬 서버의 특정 폴더에 저장했는데, 다음과 같은 문제가 있었습니다.
- 배포 환경에서 경로 인식 문제 발생
- 서버 업데이트 시마다 mkdir로 폴더를 생성해야 함
- 이전에 업로드된 파일이 삭제되거나 유지 관리가 어려움
SNS 서비스처럼 지속적인 배포와 기술 업데이트가 필요한 환경에서는 이러한 방식이 매우 불편하죠.
그래서 외부 스토리지(Firebase Storage) 를 도입하게 되었습니다.
📌 Firebase Storage 설정
1. Firebase 프로젝트 생성
- Firebase 공식 사이트에 접속
Firebase | Google's Mobile and Web App Development Platform
개발자가 사용자가 좋아할 만한 앱과 게임을 빌드하도록 지원하는 Google의 모바일 및 웹 앱 개발 플랫폼인 Firebase에 대해 알아보세요.
firebase.google.com
- Go to console → 원하는 프로젝트 선택 or 새로 생성
- 좌측 사이드바에서 빌드 → Storage 이동
2. Cloud Billing 활성화
Firebase Storage를 사용하려면 Billing 계정 등록이 필요합니다.
- 프로젝트 업그레이드 클릭
- Clound Blilling 계정 만들기를 통해 결제 수단 등록
3. 보안 모드 & 스토리지 위치 설정
- 배포용이면 프로덕션 모드 권장 (보안 규칙은 나중에도 수정 가능)
- 리전 설정 예) us-central1
해당 이미지가 나오면 Storage가 성공적으로 생성된 것입니다.
이미지의 빨간 줄이 그어진 gs://를 제외한 부분이 이후에 스토리지에 접근할 버킷입니다.
4. 보안 규칙 설정
- 보안 규칙을 설정하기 위해 규칙 탭으로 이동
보안 규칙 설정 예시
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /uploads/{fileName} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
5. 서비스 계정 키 생성
- Firebase 콘솔 → 설정 → 서비스 계정
- 새 비공개 키 생성 → 다운로드
- 이 JSON 파일을 Spring 서버 설정에 포함
📌 Spring을 통한 Firebase Storage 연동
Configuration
@Value("${firebase.config.json}")
private String firebaseConfigJson;
@Value("${firebase.storage-bucket}")
private String firebaseStorageBucket;
@PostConstruct
public void initializeFirebase() {
try {
if (firebaseConfigJson == null || firebaseConfigJson.isEmpty()) {
throw new RuntimeException("Firebase configuration is missing or empty!");
}
GoogleCredentials credentials = GoogleCredentials.fromStream(
new java.io.ByteArrayInputStream(firebaseConfigJson.getBytes())
);
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.setStorageBucket(firebaseStorageBucket)
.build();
FirebaseApp.initializeApp(options);
// CORS 설정 추가
configureCors(credentials);
} catch (IOException exception) {
exception.printStackTrace();
throw new RuntimeException("Firebase initialization failed", exception);
}
}
private void configureCors(GoogleCredentials credentials) {
try {
// 스토리지 서비스 생성
Storage storage = StorageOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
// CORS 설정
List<Cors> corsSettings = Arrays.asList(
Cors.newBuilder()
.setOrigins(Arrays.asList(Cors.Origin.of({서버 URL})))
.setMethods(Arrays.asList(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.OPTIONS))
.setResponseHeaders(Arrays.asList("Content-Type", "Access-Control-Allow-Origin"))
.setMaxAgeSeconds(3600)
.build()
);
// 버킷 이름에서 gs:// 접두사 제거
String bucketName = firebaseStorageBucket;
if (bucketName.startsWith("gs://")) {
bucketName = bucketName.substring(5);
}
// 버킷에 CORS 설정 적용
storage.update(
BucketInfo.newBuilder(bucketName)
.setCors(corsSettings)
.build()
);
System.out.println("CORS configuration applied to Firebase Storage bucket.");
} catch (Exception e) {
System.err.println("Failed to configure CORS for Firebase Storage: " + e.getMessage());
e.printStackTrace();
}
}
- firebaseConfigJson 과 firebaseStorageBucket 은 application.propertice 설정에 변수로 지정합니다.
- firebaseConfigJson : 5. 서비스 계정 키 생성 에서 생성한 Json 파일
- firebaseStorageBucket : 3. 보안모드 & 스토리지 위치 설정 후 생성된 스토리지 버킷
Service (파일 업로드)
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.Bucket;
import com.google.firebase.cloud.StorageClient;
@Override
@Transactional
public String upload(MultipartFile file) {
if (file.isEmpty()) return null;
try {
String originalFileName = file.getOriginalFilename();
String fileNameWithoutExt = originalFileName.substring(0, originalFileName.lastIndexOf("."));
String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
String romanized = KoreanRomanizer.romanize(fileNameWithoutExt);
String sanitized = romanized.replaceAll("[^a-zA-Z0-9]", "_");
String uuid = UUID.randomUUID().toString();
String saveFileName = uuid + "_" + sanitized + ext;
Bucket bucket = StorageClient.getInstance().bucket();
Blob blob = bucket.create("uploads/" + saveFileName, file.getInputStream(), file.getContentType());
// URL 생성
String downloadUrl = "https://firebasestorage.googleapis.com/v0/b/"
+ bucket.getName() + "/o/"
+ URLEncoder.encode(blob.getName(), StandardCharsets.UTF_8.toString())
+ "?alt=media";
return downloadUrl;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
- 파일을 Firevase Storage의 uploads/ 경로로 업로드 합니다.
Service (파일 다운로드)
@Override
public Resource getFile(String fileName) {
try {
Bucket bucket = StorageClient.getInstance().bucket();
Blob blob = bucket.get("uploads/" + fileName);
if (blob == null || !blob.exists()) {
return null;
}
byte[] content = blob.getContent(); // 전체 파일을 바이트 배열로 가져옴
return new ByteArrayResource(content);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Controller
@PostMapping("/upload")
public String upload(
@RequestParam("file") MultipartFile file
) {
String url = fileService.upload(file);
return url;
}
@GetMapping(value = "{fileName:.+}", produces = {
MediaType.IMAGE_JPEG_VALUE,
MediaType.IMAGE_PNG_VALUE,
MediaType.APPLICATION_PDF_VALUE,
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_OCTET_STREAM_VALUE
})
public ResponseEntity<Resource> getFile (
@PathVariable("fileName") String fileName
) {
Resource resource = fileService.getFile(fileName);
if (resource == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.body(resource);
}
이제 배포 환경에서도 서버에 별도로 디렉터리를 만들 필요 없이, 안정적이고 확장 가능한 파일 업로드/다운로드 기능을 사용할 수 있습니다.
Firebase Storage를 통해 다음과 같은 이점을 얻을 수 있습니다.
- 서버 업데이트 시 기존 파일 유지 가능
- 확장성 높은 파일 저장 구조 확보
- CDN을 통한 빠른 파일 전송 가능
이상으로 이번 포스팅을 마치겠습니다.
오늘도 즐거운 개발 되시길 바랍니다!
'개인 프로젝트' 카테고리의 다른 글
Spring + React 한글 초성 검색 기능 구현하기 (1) | 2025.04.26 |
---|---|
Google Font Icon을 활용한 아이콘 사용법 (React) (0) | 2025.04.23 |
Spring + React 파일 업로드 통합 구현 (korean-romanizer 라이브러리를 활용한 한글 파일명 오류 해결) (1) | 2025.04.21 |
Github Actions와 Cloudtype을 활용한 CI/CD 자동 배포 설정 (0) | 2025.04.20 |
React + Spring 배포 (Cloudtype - 배포) (1) | 2025.03.27 |