시작
프로젝트 생성
먼저 IntelliJ에서 새 프로젝트를 생성한다.
프로젝트구성은 SpringBoot로 종속성은 아래와 같이 간단히 추가하겠다.
build.gradle
그리고 build.gradle 에 아래와 같은 내용을 추가해 준다.
implementation platform('software.amazon.awssdk:bom:2.20.147') // AWS SDK BOM
implementation 'software.amazon.awssdk:s3' // S3 클라이언트
implementation 'org.springframework.boot:spring-boot-starter-validation' // 파일 유효성 검증에 사용
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.yml
다음 `application.yml` 을 아래와 같이 작성한다.
spring:
application:
name: demo-aws-sdk-s3
cloud:
aws:
stack:
auto: false
credentials:
accessKey: 액세스키
secretKey: 시크릿키
region:
static: 리전 주소
s3:
bucketName: 버킷이름
[Rookies 개발 2기] S3 AWS KEY 발급
먼저 IAM > 사용자에 들어가 자신의 계정을 클릭한다. 해당 화면이 보이면 액세스 키 만들기를 클릭한다. 여기서 우린 EC2와 같이 AWS컴퓨팅 서비스를 실행하기 위함이므로 해당사항을 체크
ghehf.tistory.com
키 발급에 관한 설명은 아래 게시글에 있다.
Bean 생성
다음엔 `Bean` 으로 관리하기 위해 `AwsS3 Config.java` 를 만들어보자.
public class AwsS3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3() {
log.info("Creating Amazon S3");
// 차후 제거
log.info("access-key " + accessKey);
log.info("secret-key " + secretKey);
log.info("region " + region);
// 인증정보 구성
AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
// 객체반환
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion(region)
.build();
}
}
각 키와 리전에 대한 Value를 입력하고, `AmazonS3`라는 빈을 생성하여 구성한다.
log.info는 확인을 위한 것으로 추후 삭제한다.
여기서 주의해야 할 점은 `@Value`는
import org.springframework.beans.factory.annotation.Value;
beans.factory 에 있는 어노테이션을 가져와야 한다.
Service 생성
그다음 빈을 만들었으면 `service` 를 만들어 `@Autowired` 해주어야 하는데,
아래와 같이 진행하였다.
@Service
public class AwsS3Service {
// S3와 업무 수행하는 객체
@Autowired
private AmazonS3 amazonS3;
// 버킷명 획득
@Value("${cloud.aws.s3.bucketName}")
private String bucketName;
}
}
그다음 Dto와 Controller를 생성해 간단히 구현하자.
Controller 생성
먼저 Controller를 생성하자.
package com.example.demoawssdks3.controller;
import com.example.demoawssdks3.dto.ResDto;
import com.example.demoawssdks3.service.AwsS3Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* restapi 전용, aws s3와 통신하면서 업로드 처리를 수행 컨트롤러
* /api/s3/upload
*/
@Slf4j
@RequestMapping("/api/s3")
@RestController
public class AwsS3Controller {
// 의존성 주입(DI)
@Autowired
private AwsS3Service awsS3Service;
// 실제 업로드 처리를 요청받는 라우터 구성, post,
@PostMapping("/upload")
public ResDto upload(@RequestParam("file") MultipartFile file) {
// IO , sb -> aws sdk -> s3 -> sb
String url = ""; // 업로드 결과
try{
// 1. 업로드 처리 (서비스)
url = awsS3Service.upload( file );
}catch (Exception e){
log.info("업로드 오류 " + e.getMessage());
}
return ResDto.builder()
.url(url)
.build();
}
}
Dto 생성
Dto는 url만 받아오면 되므로 최소한으로 구현하였다.
package com.example.demoawssdks3.dto;
import lombok.Builder;
import lombok.Data;
/**
* 업로드한 리소스의 퍼블릭 URL을 가지고 있는 객체
*/
@Data
public class ResDto {
private String url;
@Builder
public ResDto(String url) {
this.url = url;
}
}
Service 구현
자 이제 Service 부분을 더 구현해 보자.
- 업로드 요청을 먼저 처리해야 하며
- 확장자를 통해 고유 파일 이름으로 처리하고
- 파일데이터를 Stream으로 변환 후
- 메타데이터를 구성한다.
- 마지막으로 업로드 요청 객체(`PutObjectRequest`)를 생성 후
- 파일을 업로드 (`amazonS3.putObject()`) 후 스트림을 닫는다.
public String upload(MultipartFile file) {
// 파일이 정상인지 검사
if(file.isEmpty() || Objects.isNull(file.getOriginalFilename())) {
throw new Exception("파일 누락 혹은 비워있음");
}
// 업로드한 파일 종류 처리
return checkAndUpload( file );
}
private String checkAndUpload(MultipartFile file) {
// 원본 파일명 획득
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename : " + originalFilename);
// .(확장자)을 중심으로 이름과 확장자 획득
String ext = originalFilename.substring(originalFilename.lastIndexOf(".")+1);
System.out.println("ext : " + ext);
// 이름+UUID등의 고유값 묶어서 파일명 구성
String s3UploadName = UUID.randomUUID().toString().substring(0,12) + "-" + originalFilename;
System.out.println("s3UploadName : " + s3UploadName);
String url="";
try {
// 스트림 구성
byte[] bytes = IOUtils.toByteArray(file.getInputStream());
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 마임타임 구성 -> 메타데이터
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/" + ext);
metadata.setContentLength(bytes.length);
// s3 업로드 수행
PutObjectRequest obj = new PutObjectRequest(
bucketName, // 버킷의 이름
s3UploadName,// 업로드될 파일명
byteArrayInputStream, // 데이터를 전달할 스트림
metadata // 메타 정보(파일)
);
amazonS3.putObject( obj ); // 업로드
// 스트림 닫기
byteArrayInputStream.close();
// 퍼블릭 URL 획득
url = amazonS3.getUrl(bucketName, s3UploadName).toString();
}catch (Exception e) {
System.out.println("파일 업로드간 오류 : " + e.getMessage());
}
// 반환
return url;
그렇다면 html을 통해 S3 업로드를 진행 후 업로드된 해당 url을 반환받는 과정까지 진행해 보겠다.
결과
로컬환경에서 파일 선택 후 업로드를 진행하면
아래와 같이 Public URL을 받을 수 있고
아마존 S3 버킷에서도 고유한 이름으로 저장된 걸 확인할 수 있다.
느낀 점
S3를 사용해 파일 업로드 기능을 구현하면서, 단순히 코드를 작성하는 것을 넘어 클라우드 스토리지의 작동 원리와 효율적인 파일 관리를 배우는 좋은 기회가 되었다.
가장 먼저 느낀 점은 안정적인 파일 관리의 중요성이다. 파일 이름이 중복될 경우를 대비해 UUID를 사용해 고유한 이름을 생성하거나, 확장자와 메타데이터를 설정해 적절한 콘텐츠 타입을 지정하는 등의 작업이 단순히 파일 업로드가 아니라, 이후 데이터를 활용하는 데 큰 영향을 미친다는 걸 깨달았다.
또한, 스트림 처리와 효율적인 자원 관리를 배우는 계기가 되었다. 파일을 바이트 배열로 변환하고 스트림으로 처리하는 과정에서 자원을 낭비하지 않도록 주의해야 한다는 것을 알게 되었다.
결론적으로, 이번 작업은 클라우드와 파일 관리의 기초부터 실무적인 활용까지 폭넓게 배우는 경험이었고, 앞으로도 이런 기술을 활용해 더 나은 서비스를 제공할 수 있을 것이라는 자신감을 얻었다.
'SK 루키즈 > Cloud' 카테고리의 다른 글
[Rookies 개발 2기] AWS 서버리스 개념과 ECS와 Lambda (0) | 2025.01.24 |
---|---|
[Rookies 개발 2기] MSA와 EurekaServer 세팅 (0) | 2025.01.24 |
[Rookies 개발 2기] S3 AWS KEY 발급 (0) | 2025.01.23 |
[Rookies 개발 2기] React + SpringBoot + MySql CI/CD (2) (0) | 2025.01.20 |
[Rookies 개발 2기] React + SpringBoot + MySql CI/CD (1) (1) | 2025.01.20 |