
안녕하세요 데브당에입니다.
오늘은 AWS S3 Bucket를 이용하여 파일 업로드하는 법에 대해 알아보겠습니다.
들어가며
저는 프로젝트에서 프로필 사진 업로드, 게시판 파일첨부, 수업자료 업로드 부분에 S3 버킷을 적용했습니다.
버킷 생성 및 설정, 스프링부트 환경설정만 잘 설정해둔다면 코드상의 로직은 비슷하게 구성되기 때문에 비교적 수월하게 구현할 수 있을거라 생각합니다.
먼저 저희 프로젝트에서 구현했던 게시판 화면을 보여드리겠습니다.
게시글 등록시 파일을 첨부할 수 있고, 조회시에는 다운로드 받을 수 있도록 구현했습니다.


그럼 지금부터 S3 버킷 생성부터 Springboot 환경설정 및 Service, Controller 작성, React 코드까지 알아보겠습니다.
S3 버킷 생성
















[SpringBoot-1] 환경설정
- application.properties(S3버킷 IAM 생성 시 다운받은 CSV에 있는 키 작성)
# S3 Bucket cloud.aws.credentials.accessKey=accessKey를입력해주세요 cloud.aws.credentials.secretKey=secretKey를입력해주세요 cloud.aws.stack.auto=false # AWS S3 Service bucket cloud.aws.s3.bucket=bucket-name을 작성해주세요 cloud.aws.region.static=ap-northeast-2 # AWS S3 Bucket URL cloud.aws.s3.bucket.url=bucket주소를작성해주세요 # multipart 사이즈 설정 spring.http.multipart.max-file-size=1024MB spring.http.multipart.max-request-size=1024MB - gradle.build
// S3 Bucket compile 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'com.amazonaws:aws-java-sdk:1.11.404'
[SpringBoot-2] Config 파일 생성
@Configuration public class S3Config { @Value("${cloud.aws.credentials.access-key}") private String accessKey; @Value("${cloud.aws.credentials.secret-key}") private String secretKey; @Value("${cloud.aws.region.static}") private String region; @Bean @Primary public BasicAWSCredentials awsCredentialsProvider(){ BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey); return basicAWSCredentials; } @Bean public AmazonS3 amazonS3() { AmazonS3 s3Builder = AmazonS3ClientBuilder.standard() .withRegion(region) .withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider())) .build(); return s3Builder; } @Bean public AmazonS3Client amazonS3Client() { BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); return (AmazonS3Client) AmazonS3ClientBuilder.standard() .withRegion(region) .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) .build(); } }
[SpringBoot-3] AwsS3Service
@Value("${cloud.aws.s3.bucket}") private String bucket; private final AmazonS3Client amazonS3Client; private final NoticeFileRepository noticeFileRepository; @Transactional @Override public List<String> uploadFile(User user, Notice notice, List<MultipartFile> multipartFile) { List<String> fileNameList = new ArrayList<>(); multipartFile.forEach(file -> { String fileName = createFileName(file.getOriginalFilename()); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(file.getSize()); objectMetadata.setContentType(file.getContentType()); try(InputStream inputStream = file.getInputStream()) { amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) .withCannedAcl(CannedAccessControlList.PublicRead)); } catch(IOException e) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다."); } // 원본파일이름과 변경된 파일이름을 DB에 저장 -> 다운로드시 필요 NoticeFile noticefile = NoticeFile.builder() .newFileName(fileName) .originFileName(file.getOriginalFilename()) .user(user) .notice(notice) .build(); noticeFileRepository.save(noticefile); fileNameList.add(fileName); }); return fileNameList; }
[SpringBoot-4] NoticeService
private final NoticeRepository noticeRepository; @Transactional @Override public int registerNotice(String accessToken, List<MultipartFile> multipartFile, NoticeRegisterRequestDto noticeRegisterRequestDto) { // 게시글 정보인 RequestDto를 DB에 저장하기 Notice notice = Notice.builder() .title(noticeRegisterRequestDto.getTitle()) .content(noticeRegisterRequestDto.getContent()) .hit(0) .build(); noticeRepository.save(notice); // 게시글 등록 시, 파일첨부를 안 할 수 있기 때문에 조건 추가 if(multipartFile != null) awsS3Service.uploadFile(user, notice, multipartFile); return 200; }
[SpringBoot-5] Controller
@PostMapping(consumes = {"multipart/form-data"}) @ApiOperation(value = "알림장 등록하기", notes="<strong>선생님이 작성한 알림장을 등록한다.</strong>") @ApiResponses({ @ApiResponse(code=201, message="알림장이 정상적으로 등록되었습니다."), @ApiResponse(code=401, message="인증되지 않은 사용자입니다."), @ApiResponse(code=408, message="학생입니다."), @ApiResponse(code=409, message="알림장 등록을 실패했습니다. ") }) public ResponseEntity<? extends BaseResponseDto> regist( @ApiIgnore @RequestHeader("Authorization") String accessToken, // 첨부파일은 multipart/form-data 형식으로 받기때문에 어노테이션 타입은 RequestPart로 작성 // 파일첨부를 안할수도 있기 때문에 required=false 로 설정 @ApiParam(value="파일(여러 파일 업로드 가능)") @RequestPart(required = false) List<MultipartFile> multipartFile, // 게시글 등록정보도 FrontEnd에서 multipart/form-data 형식으로 받기때문에 어노테이션 타입은 RequestPart로 작성 @ApiParam(value = "등록할 알림장", required = true) @RequestPart NoticeRegisterRequestDto noticeRegisterRequestDto){ int result = noticeService.registerNotice(accessToken, multipartFile, noticeRegisterRequestDto); if(result == 200) return ResponseEntity.status(200).body(BaseResponseDto.of(200, "Success")); else if(result==401) return ResponseEntity.status(401).body(BaseResponseDto.of(401, "Fail")); else if(result==408) return ResponseEntity.status(408).body(BaseResponseDto.of(408, "Fail")); else return ResponseEntity.status(409).body(BaseResponseDto.of(409, "Fail")); }
React 코드작성
let formData = new FormData(); if (data.files) { for (let i = 0; i < data.files.length; i++) { formData.append("multipartFile", data.files[i]); } } formData.append( "noticeRegisterRequestDto", new Blob( [ JSON.stringify({ title: data.title, content: editorRef.current.getInstance().getHTML(), }), ], { type: "application/json" } ) );
마치며
S3 버킷을 처음 사용해본 것이었지만 기본적인 설정과 포맷을 알고나면 구현하는데는 어려움이 없는 것 같았습니다. 하지만 Controller가 정상적으로 동작하는지 확인하는 과정에서 어려움이 있었습니다. 저는 프로젝트 개발 시, API Docs Tool로 Swagger-ui 를 사용하고 있는데 첨부파일과 별도의 DTO를 함께 요청할때는 Swagger-ui에서 요청/응답을 확인할 수 없었습니다. 구글링을 하다보니 많은 사람들이 파일 업로드 시에 Postman이라는 Tool을 많이 사용한다는 것을 알게 되었고 저도 Postman을 사용해서 요청/응답이 정상적으로 이루어지는지 확인할 수 있었습니다.
FE<->BE 간에 데이터를 주고 받을 때도 주고받는 데이터의 type이 매칭되지 않아 많은 시행착오를 겪었습니다. 결과적으로 Controller에서는 파일과 DTO 모두 @RequesstPart 어노테이션을 붙여주었고, React 에서도 파일, DTO 모두 FormData에 append 해주어 파일 업로드 기능이 정상적으로 동작하게 했습니다.
전체 프로젝트 코드는 아래 GitHub를 참고해주시기 바랍니다.
GitHub - dayaeLee777/DrawingDream: 💫 세상에서 가장 편한 학교, Drawing Dream 에서 여러분의 꿈을 그려 보
💫 세상에서 가장 편한 학교, Drawing Dream 에서 여러분의 꿈을 그려 보세요. Contribute to dayaeLee777/DrawingDream development by creating an account on GitHub.
github.com
'Programming > Web' 카테고리의 다른 글
[Web] MVC 패턴, Model1, Model2 란? 구조와 장단점까지 알아보기 (0) | 2022.01.06 |
---|---|
[Web] REST API, 기초부터 정확히 이해하기 (1) | 2022.01.05 |