[TIL] S3로 파일 업로드

woonie·2022년 3월 1일
0

TIL

목록 보기
43/64
post-custom-banner

마지막 프로젝트를 진행중이며 유저가 업로드 하는 이미지 파일을 S3 클라우드에 저장하는 방식을 사용헤보기로 했다.
먼저 AWS S3 Bucket을 생성하고 코드 구현을 했다.

S3 버킷 생성

  • S3에서 버킷 만들기 클릭

1) 버킷 이름과 리전 설정

  • 버킷 이름은 고유값으로 설정

2) 퍼블릭 액세스 설정
※ 외부에 S3을 공개할 경우 모든 퍼블릭 액세스 차단을 체크 해제하고, 공개하지 않는다면 체크를 해주면 된다.

3) 버킷 정책 생성

  • 버킷 생성까지 완료 되었으면 외부에서 접근이 가능하도록 버킷 정책을 설정해줘야 한다.
  • 생성된 버킷명을 클릭해서 들어가면 아래와 같은 탭이 있는데 [권한] 탭을 클릭해서 들어가면
    버킷 정책이 있다. 편집으로 들어가자.

  • 정책 생성기를 클릭하고 설정해야한다.
    (버킷 ARN은 복사를 미리해둔다)

  • 버킷 생성기에 들어가면 아래와 같은 화면이 나올거고 해당 항목들을 채워 나간다.

  • Select Type of Policy - S3 Bucket Policy

  • Effect - Allow

  • Principal - *

  • Action - GetObject, PutObject

  • ARN - arn:aws:s3:::{버킷이름}/*

Add Statement 클릭 -> 아래 Generate Policy를 클릭하여 생성된 정책을 복사 후
S3 정책 편집에 돌아가서 수정해주면 된다.

4) access key 발급

  • S3에 접근하려면 별도의 인증 과정이 필요하다.
  • 액세스 키를 가지고 있는 사람만 자격을 증명할 수 있다.
  • 액세스 키는 AWSAccessKeyId와 AWSSecretKey로 구성된다.
  • 보안 자격 증명(IAM) > 액세스 키 > 새 액세스 키 만들기
  • '키 파일 다운로드' 클릭(반드시 다운받고 따로 보관하자)
  • AccessKeyId와 SecretKey는 외부에 노출되어서는 안된다.

AWS 의존성 추가 및 설정

  • build.gradle
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.1.RELEASE'

  • apllication.properties
ccloud.aws.credentials.accessKey= 사용자 엑세스 키
cloud.aws.credentials.secretKey= 비밀 엑세스 키 
cloud.aws.stack.auto=false

# AWS S3 Service bucket
cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름)
cloud.aws.region.static=ap-northeast-2 (버킷 지역/서울)

# AWS S3 Bucket URL
cloud.aws.s3.bucket.url=https://s3.ap-northeast-2.amazonaws.com/버킷이름

Config 및 Util 클래스 생성

  • S3 Config Class

@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
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
    }

}
  • S3 Service
public class S3Uploader {

    private final AmazonS3Client amazonS3Client;

    @Value("${cloud.aws.s3.bucket}")
    public String bucket;  // S3 버킷 이름

    // 전달 받은 데이터를 바로 S3에 업로드하는 메소드
    // 1. 사전 준비 - 메타데이터와 파일명 생성
    // 2. S3에 전달 받은 파일 업로드
    // 3. S3에 저장된 파일 이름과 주소 반환

    // 파라미터로 multipartFile(업로드하려는 파일)과 dirName(이 파일을 업로드하고 싶은 S3 버킷의 폴더 이름)을 받는다.
    public HashMap<String, String> uploadImage(MultipartFile multipartFile, String dirName) throws IOException {

        // 0. 이미지 파일인지 체크
        isImage(multipartFile);

        // 1. 사전 준비
        // 1-1 메타데이터 생성
        // InputStream을 통해 Byte만 전달되고 해당 파일에 대한 정보가 없기 때문에 파일의 정보를 담은 메타데이터가 필요하다.
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(multipartFile.getContentType());
        metadata.setContentLength(multipartFile.getSize());

        // 1-2 S3에 저장할 파일명 생성
        // UUID 사용 이유 : 이름이 같은 파일들이 서로 덮어쓰지 않고 구분될 수 있도록
        String fileName = createFileName(multipartFile);
        String uploadImageName = dirName + "/" + UUID.randomUUID() + fileName;

        // 2. s3로 업로드
        amazonS3Client.putObject(new PutObjectRequest(bucket,uploadImageName, multipartFile.getInputStream(),metadata)
                .withCannedAcl(CannedAccessControlList.PublicRead));
        // S3에 업로드한 이미지의 주소를 받아온다.
        String uploadImageUrl = amazonS3Client.getUrl(bucket, uploadImageName).toString();


        // 4. S3에 저장된 파일 이름과 주소 반환
        HashMap<String, String> imgInfo = new HashMap<>();
        imgInfo.put("fileName", uploadImageName);
        imgInfo.put("img",uploadImageUrl);
        return imgInfo;
    }

    // 파일 삭제하기
    public void deleteImageFile(String fileName) {
        amazonS3Client.deleteObject(bucket, fileName);
    }
    
    // 파일 이름 생성 메소드
    private String createFileName(MultipartFile multipartFile) {

        String name = multipartFile.getOriginalFilename();
        String ext = "";

        // 확장자와 파일명 분리
        if (name.contains(".")) {
            int position = name.lastIndexOf(".");
            ext = name.substring(position+1);
            name = name.substring(0,position);
        }

        // 파일 이름의 길이가 길면 100자로 자르기 (디비의 varchar(255) 제한에 걸리지 않으려고)
        if (name.length()>100){
            name = name.substring(0,100);
        }

        // S3에 저장할 파일 이름 생성
        String fileName = !ext.equals("")?name+"."+ext:name;

        return fileName;
    }

    // 이미지 파일인지 확인하는 메소드
    private void isImage(MultipartFile multipartFile) throws IOException {

        // tika를 이용해 파일 MIME 타입 체크
        // 파일명에 .jpg 식으로 붙는 확장자는 없앨 수도 있고 조작도 가능하므로 MIME 타입을 체크하는 것이 좋다.
        Tika tika = new Tika();
        String mimeType = tika.detect(multipartFile.getInputStream());

        // MIME타입이 이미지가 아니면 exception 발생생
        if (!mimeType.startsWith("image/")) {
            throw new CustomException(ErrorCode.NOT_IMAGES);
        }
    }
}
  • S3 Controller
@RequiredArgsConstructor
@RestController
public class AwsController {

    private final S3Uploader s3Uploader;

    @PostMapping("/images/upload")
    public HashMap<String,String> upload(@RequestParam("images") MultipartFile multipartFile) throws IOException {

        String imgUrl = "";
        String fileName = "";
        HashMap<String,String> imgInfo = s3Uploader.upload(multipartFile, "static");
        imgUrl = imgInfo.get("img");
        fileName = imgInfo.get("fileName");

        HashMap<String,String> imageUrl = new HashMap<>();
        imageUrl.put("url",imgUrl);

        return imageUrl;
    }

    // 사진 삭제 테스트
    @DeleteMapping("/image/delete")
    public String deleteimage(@RequestParam("path") String fileName) {
        s3Uploader.deleteFile(fileName);
        return "삭제완료";
    }
}
profile
동료들과 함께하는 개발의 중요성에 관심이 많습니다. 언제나 호기심을 갖고 꾸준히 노력하는 개발자로서 성장하고 있습니다.
post-custom-banner

0개의 댓글