컬쥐네 다락방

AWS S3를 이용한 파일 업로드 본문

공부방/항해 99

AWS S3를 이용한 파일 업로드

코딩하는 갱얼쥐 2021. 5. 4. 20:13

미니 프로젝트를 진행하면서부터 프로필 수정이나 게시글 업로드 과정에서 사진 파일 등록 기능이 필요해지면서 공부했던 파일 업로드 기능입니다. 

 

AWS의 S3를 사용해서 입력받은 파일 데이터를 저장하고, 이미지 url을 반환하여 이 Url을 이용해 사진을 보여주는 방식을 주로 사용했습니다 :) 

 

-FileUploadService 

@RequiredArgsConstructor
@Service
public class FileUploadService {

    private final S3Service s3Service;
    public String uploadImage(MultipartFile file) {
        String fileName = createFileName(file.getOriginalFilename());

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(file.getContentType());

        try (InputStream inputStream = file.getInputStream()) {
            s3Service.uploadFile(inputStream, objectMetadata, fileName);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format("파일 변환 중 에러가 발생하였습니다 (%s)", file.getOriginalFilename()));
        }
        return s3Service.getFileUrl(fileName);
    }

    private String createFileName(String originalFileName) {
        return UUID.randomUUID().toString().concat(getFileExtension(originalFileName));
    }

    private String getFileExtension(String fileName) {
        try {
            return fileName.substring(fileName.lastIndexOf("."));
        } catch (StringIndexOutOfBoundsException e) {
            throw new IllegalArgumentException(String.format("잘못된 형식의 파일 (%s) 입니다", fileName));
        }
    }

}

 

입력받은 파일에서 파일의 원래 이름, 메타 데이터, inputStream을 읽어오고 이 정보를 이용해서 S3에 저장을 요청합니다. 여기서 메타 데이터는 데이터를 설명해주는 데이터, inputStream은 바이트 단위의 데이터 라고 이해했습니다. 

특히 파일을 저장할 땐 원래 파일 명을 사용하기 보다는 파일 명을 이용해 숫자와 영어로 구성된 랜덤 이름을 만들어 저장! 

 

이 외에도 이런 작업이 매끄럽게 이어지도록 기능 별로 클래스를 만들어서 관리했습니다 .

-S3Service

//인터페이스인 S3Service를 FileUploadService에서 주입받아서 런타임 시점에 오브젝트 관계를 갖도록 해줌
public interface S3Service {

    void uploadFile(InputStream inputStream, ObjectMetadata objectMetadata, String fileName);

    String getFileUrl(String fileName);

}

 

-S3 Service Impl

@RequiredArgsConstructor
@Component
public class S3ServiceImpl implements S3Service {

    private final AmazonS3Client amazonS3Client;
    private final S3Component s3Component;

    @Override
    public void uploadFile(InputStream inputStream, ObjectMetadata objectMetadata, String fileName) {
        amazonS3Client.putObject(new PutObjectRequest(s3Component.getBucket(), fileName, inputStream, objectMetadata).withCannedAcl(CannedAccessControlList.PublicRead));
    }

    //aws-cloud-starter-aws 라이브러리에서 제공하는 AmazonS3Client를 사용해서 다음과 같이 파일을 업로드 하고(uploadFile),
    //아래 메소드는 업로드한 파일의 URI를 가져오는 메소드입니다.

    @Override
    public String getFileUrl(String fileName) {
        return String.valueOf(amazonS3Client.getUrl(s3Component.getBucket(), fileName));
    }
    //S3Component를 주입받아서 프로퍼티에 추가한 버킷 정보를 가져와서 awsS3Client.putObject(...)를 통해 AWS S3로 파일을 업로드합니다.
    //그리고 awsClient.getUrl(...)을 통해 업로드된 파일의 URI을 가져옵니다. 다음은 -> FileUploadService

}

-S3 Component

//ConfigurationProperties(prefix="cloud.aws.s3")을 통해 프로퍼티의 값을 불러오도록 합니다.
// @Setter 필요, 또한 S3ServiceImpl에서 빈 주입 받기 위해 빈으로 등록
@Getter
@Setter
@ConfigurationProperties(prefix = "cloud.aws.s3")
@Component
public class S3Component {

    private String bucket;

}

Component에서는 prefix를 통해 properties에서 bucket의 값을 받도록 만들어져있다. 

이 bucket의 이름을 가져다가 다른 클래스들에서 저장될 위치를 정해줬습니다.

-실제 Controller에서 사용된 모습 

    @RequestMapping("/api/profile")
    public UserDto profileChange(@RequestPart String userData, @RequestParam(required = false) MultipartFile file, HttpServletRequest httpServletRequest) {
        JSONObject userJson = new JSONObject(userData);
        //토근에서 사용자 정보 추출
        String token = jwtTokenProvider.resolveToken(httpServletRequest);
        String email = jwtTokenProvider.getUserPk(token);
        User member = userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException("일치하는 E-MAIL이 없습니다"));
        if(file == null){
                UserDto userDto = new UserDto(member,userJson);
                userService.update(userDto);
                return userDto;
        } else{
            String fileUrl = fileUploadService.uploadImage(file);
            //해당 사용자의 프로필 업데이트
            UserDto userDto = new UserDto(member,userJson,fileUrl);
            userService.update(userDto);
            return userDto;
        }
    }

프로필 수정에서는 사용자가 프로필을 수정하지 않을 수 있기 때문에 프로필 사진을 등록했을 때와 등록하지 않았을 때로 나눠서 이미지 데이터를 저장할 것인지, 아니면 현재 데이터를 그대로 사용할 것인지 나눠줬습니다 .

Comments