한이음 프로젝트

[한이음] 스프링 AWS S3 파일 업로드 및 controller 에러 해결

딤섬뮨 2022. 8. 29. 11:48
728x90

파일 업로드 기능을 구현하려고 했었는데 멘토님이 AWS S3를 사용하시면 된다고 조언을 해주셨다.

 

오늘은 S3를 이용하여 연결하는 작업만 작성하려고 한다. S3버킷 생성 같은 건 다른 게시물을 참조하시길..


1. 기본설정

  • build.gradle 
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
  • application.yml
cloud:
  aws:
    s3:
      bucket: //aws에 생성한 버킷 이름 적기 
    credentials:
      access-key: //aws 버킷 access key
      secret-key: //aws 버킷 secret-key
    region:
      static: ap-northeast-2
      auto: false
    stack:
      auto: false
logging:
  level:
    com:
      amazonaws:
        util:
          EC2MetadataUtils: ERROR

 + cloud.aws.stack.auto 속성을 false로 지정하지 않으면 다음과 같은 StackTrace가 발생합니다.

이는 기본적으로 서비스가 Cloud Formation 스택 내에서 실행된다고 가정하기 때문에 그렇지 않은 경우 임의로 false 값으로 설정을 해줘야 된다고 한다.

  • spring boot application 

적용하고자 하는 모듈 application에 다음과 같이 작성해준다. 나는 bbl-career 모듈에 적용을 원하는 상황이다.

@SpringBootApplication
@EnableJpaAuditing
@EnableFeignClients
public class BblCareerApplication {

	static {
		System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true");
	}

	public static void main(String[] args) {
		SpringApplication.run(BblCareerApplication.class, args);
	}

}

 

설정은 끝


이제 본격적으로 스프링과 연동해보겠다.

나는 bbl-career 안에 있는 서비스 중 인사 내역을 업로드할 때

파일 업로드를 통해 그 안에 성적 증명서라든가, 졸업 증명서를 첨부하게끔 만들고 싶다.

 

인사 내역  = 인사 정보 + 파일 


1. FileDto


@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class FileDto {

  private String id;
  private String name;
  private String format;
  private String path;
  private long bytes;

  @Builder.Default
  private LocalDateTime createdAt = LocalDateTime.now();

  public static FileDto multipartOf(MultipartFile multipartFile) {
    final String fileId = MultipartUtil.createFileId();
    final String format = MultipartUtil.getFormat(multipartFile.getContentType());
    return FileDto.builder()
        .id(fileId)
        .name(multipartFile.getOriginalFilename())
        .format(format)
        .path(MultipartUtil.createPath(fileId, format))
        .bytes(multipartFile.getSize())
        .build();
  }
}

2.Career Controller

원래는 파일 업로드만 하는 컨트롤러를 따로 만들었는데 한 컨트롤러 내에서 처리하고 싶어서 기존의 career controller에 내용을 추가했다.

 @ApiOperation("개인 인사 내역 등록")
  @PostMapping()
  public ResponseFormat createCareer(@RequestBody @Valid CareerDto.CREATE careerDto) {
    careerService.newCareer(careerDto);
    return ResponseFormat.ok();
  }

원래 컨트롤러는 다음과 같았다.

파일 파라미터를 받길 원해서 처음에는 @RequestBody로 DTO를 받고 @RequestPart로 파일을 받으려고 했는데

ontent type 'application/octet-stream' not supported

이런 에러가 났고 삽질을 통해 해결했다

방법은 둘 다 @RequestPart 어노테이션을 달아주고 위에 타입을 작성하면 된다.

  @ApiOperation("개인 인사 내역 등록")
  @PostMapping(value = "/create",consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
  public ResponseFormat createCareer(@RequestPart(value = "createDto") CareerDto.CREATE careerDto,@RequestPart(value = "file")
      MultipartFile multipartFile) {
    careerService.newCareer(careerDto,multipartFile);
    return ResponseFormat.ok();
  }

3. CareerService에서 FileService 호출 

 @Override
  //TODO : 예외 처리 세분화
  public void newCareer(CREATE newCareer, MultipartFile multipartFile) {

    //feign client 권한 검사
    try {
      isAvailabe(newCareer.getMemberIdentity());
    } catch (UnauthorizedException e) {
      throw e;
    }
    CareerEntity careerEntity = CareerEntity.of(newCareer);

    try {
      careerRepository.save(careerEntity);
      fileUploadService.save(multipartFile); // 파일 전달
    } catch (DataIntegrityViolationException e) {
      throw new DuplicatedException(DUPLICATED_MEMBERIDENTITY);

    }

  }

 

기존 로직에 fileUploadService를 주입받고 거기 안에 있는 save 함수를 호출하는 코드를 추가해줬다.

 

4. File Service

@Service
@RequiredArgsConstructor
public class FileUploadService {
  private final AmazonS3ResourceStorage amazonS3ResourceStorage;

  public FileDetail save(MultipartFile multipartFile) {
    FileDetail fileDetail = FileDetail.multipartOf(multipartFile); //file Dto생성
    amazonS3ResourceStorage.store(fileDetail.getPath(), multipartFile); //레파지토리에 저장
    return fileDetail; 
  }
}

DTO로 변환한 뒤 레파지토리에 저장을 한다

 

5. AmazonS3ResourceStorage

이제 여기서 S3버킷에 저장을 해준다.


@Component
@RequiredArgsConstructor
public class AmazonS3ResourceStorage {

  @Value("${cloud.aws.s3.bucket}") // 버킷 이름 작성
  private String bucket;
  private final AmazonS3Client amazonS3Client;

  public void store(String fullPath, MultipartFile multipartFile) {
    File file = new File(MultipartUtil.getLocalHomeDirectory(), fullPath);
    try {
      multipartFile.transferTo(file);
      amazonS3Client.putObject(new PutObjectRequest(bucket, fullPath, file)
          .withCannedAcl(CannedAccessControlList.PublicRead));
    } catch (Exception e) {
      throw new RuntimeException();
    } finally {
      if (file.exists()) {
        file.delete();
      }
    }
  }
}

 

여기까지 하면 끝인데!!!


에러

 

근데 swagger에는 DTO입력창이 계속 안 뜬다

왜...ㅜㅜㅜㅜ

그렇게 swagger는 문제를 해결 못해서 postman으로 테스트해봤다.

중요 - postman에서 content type에 application/json을 명시해줘야 작동한다!

 

잘 올라갔다!

728x90