티스토리 뷰

이번장에는 Spring Boot와 AWS SDK를 이용하여 AWS S3에 이미지/파일 정적리소스들을 등록, 조회, 삭제하는 내용에 대해 정리해보고자한다. 그전에 AWS S3에 버킷생성 / IAM 사용자를 생성후 S3 액세스 권한을 부여해줘야한다.

 

해당작업을 시작하기전에 위 두가지가 완료되지 않았다면 아래링크를 통해 먼저준비해두자

S3버킷 및 객체 생성링크부분에서는 퍼블릭 액세스 접근설정만 하면된다. ('객체생성' 전 단계)

https://kim-jong-hyun.tistory.com/84

 

2. AWS - S3 버킷 및 객체 생성

이번장에는 S3 버킷을 생성하고 버킷내부에 객체를 생성해보자. AWS 콘솔접속 AWS에 로그인하여 콘솔화면 검색창에 S3를 검색해서 들어간다. AWS 버킷 생성하기 화면우측에 있는 버킷 만들기 버튼

kim-jong-hyun.tistory.com

 

 

https://kim-jong-hyun.tistory.com/85

 

3. AWS - IAM으로 사용자 추가 및 S3 액세스 권한추가

https://kim-jong-hyun.tistory.com/84 이전에 작성한 글에선 AWS S3 콘솔에 직접 객체를 생성, 조회, 삭제를 해보았는데 이번장에는 AWS에서 제공하는 SDK를 이용하여 애플리케이션에서 AWS 리소스를 생성 및

kim-jong-hyun.tistory.com

 

Spring 환경세팅

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-aws</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

Spring Cloud Aws 의존성을 추가하자.

application.yaml 파일작성

spring:
  servlet:
    multipart:
      max-request-size: 10MB
      max-file-size: 10MB
server:
  port: 80
cloud:
  aws:
    credentials:
      access-key: accessKey값
      secret-key: secretKey값
    s3:
      bucket: 버킷네임
    region:
      static: ap-northeast-2
    stack:
      auto: false

AwsS3Config 작성

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AwsS3Config {

    @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 BasicAWSCredentials basicAWSCredentials() {
        return new BasicAWSCredentials(accessKey, secretKey);
    }

    @Bean
    public AmazonS3 amazonS3(BasicAWSCredentials basicAWSCredentials) {
        return AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .build();
    }
}

accessKey, secretKey, region을 받아와서 AmazonS3 객체를 Bean으로 등록한다.

AwsS3Controller 작성

import com.cloud.aws.dto.AwsS3;
import com.cloud.aws.service.AwsS3Service;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@RestController
@RequestMapping("/s3")
@RequiredArgsConstructor
public class AwsS3Controller {

    private final AwsS3Service awsS3Service;

    @PostMapping("/resource")
    public AwsS3 upload(@RequestPart("file") MultipartFile multipartFile) throws IOException {
        return awsS3Service.upload(multipartFile,"upload");
    }

    @DeleteMapping("/resource")
    public void remove(AwsS3 awsS3) {
        awsS3Service.remove(awsS3);
    }
}

awsS3Service.upload 메서드 두번째 인자인 'upload'는 버킷하위에 생성할 폴더이름이다. 즉, 이미지를 업로드하고나서 해당이미지는 버킷네임/upload/ 디렉토리에 생성된다.

AwsS3Service 작성

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.cloud.aws.dto.AwsS3;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class AwsS3Service {

    private final AmazonS3 amazonS3;

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    public AwsS3 upload(MultipartFile multipartFile, String dirName) throws IOException {
        File file = convertMultipartFileToFile(multipartFile)
                .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File convert fail"));

        return upload(file, dirName);
    }

    private AwsS3 upload(File file, String dirName) {
        String key = randomFileName(file, dirName);
        String path = putS3(file, key);
        removeFile(file);

        return AwsS3
                .builder()
                .key(key)
                .path(path)
                .build();
    }

    private String randomFileName(File file, String dirName) {
        return dirName + "/" + UUID.randomUUID() + file.getName();
    }

    private String putS3(File uploadFile, String fileName) {
        amazonS3.putObject(new PutObjectRequest(bucket, fileName, uploadFile)
                .withCannedAcl(CannedAccessControlList.PublicRead));
        return getS3(bucket, fileName);
    }

    private String getS3(String bucket, String fileName) {
        return amazonS3.getUrl(bucket, fileName).toString();
    }

    private void removeFile(File file) {
        file.delete();
    }

    public Optional<File> convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
        File file = new File(System.getProperty("user.dir") + "/" + multipartFile.getOriginalFilename());

        if (file.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(file)){
                fos.write(multipartFile.getBytes());
            }
            return Optional.of(file);
        }
        return Optional.empty();
    }

    public void remove(AwsS3 awsS3) {
        if (!amazonS3.doesObjectExist(bucket, awsS3.getKey())) {
            throw new AmazonS3Exception("Object " +awsS3.getKey()+ " does not exist!");
        }
        amazonS3.deleteObject(bucket, awsS3.getKey());
    }
}

S3에 이미지를 업로드하는 순서는 다음과 같다.

  • S3에 업로드하기위해선 MultipartFile을 File객체로 변환후 현재 프로젝트 경로에 업로드 한다.
  • 파일명에 UUID를 붙혀서 S3에 업로드후 업로드된 이미지의 key값과 path를 반환한다. key는 객체이름이고 path는 해당객체의 절대경로값이다.
  • 현재 프로젝트 경로에 생성되었던 파일을 제거한다.

AwsS3 작성

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class AwsS3 {
    private String key;
    private String path;

    public AwsS3() {

    }

    @Builder
    public AwsS3(String key, String path) {
        this.key = key;
        this.path = path;
    }
}

index.html 작성

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>Aws S3 File Upload</title>
</head>
<body>
  <div style="height: 50px;">
      <input type="file" id="file" style="display: none;">
      <label for="file" style="color: blue;cursor: pointer">업로드</label>
      <input type="button" id="remove" style="display: none;">
      <label for="remove" style="color: red;cursor: pointer">삭제</label>
  </div>
  <div>
      <img id="img"/>
  </div>
  <script type="text/javascript">
    document.getElementById("file").addEventListener("change", uploadResource);
    document.getElementById("remove").addEventListener("click", removeResource);

    function uploadResource() {
        const file = document.getElementById("file");
        const formData = new FormData();
        formData.append("file", file.files[0]);

        fetch("/s3/resource", {
            method : "POST"
            , body : formData
        })
        .then(result => result.json())
        .then(data => {
            document.getElementById("img").setAttribute("src", data.path);
            document.getElementById("remove").setAttribute("key", data.key)
        })
        .catch(error => console.log(`error => ${error}`));
    }
    function removeResource() {
        const key = document.getElementById("remove").getAttribute("key");
        if (!key) {
            return;
        }
        const formData = new FormData();
        formData.append("key", key);

        fetch("/s3/resource", {
            method : "DELETE"
            , body : formData
        })
        .then(result => {
            if (result.ok && result.status === 200) {
                alert("해당 이미지가 삭제되었습니다.");
                document.getElementById("img").removeAttribute("src");
            }
        })
        .catch(error => console.log(`error => ${error}`));
    }
  </script>
</body>
</html>

이미지를 업로드하면 S3에 새로운 객체를 생성하고 객체의 url값을 반환한다. 이미지를 삭제하면 S3에 해당이미지를 삭제한다.

테스트

이미지를 등록한다.



해당버킷에 upload라는 폴더가 생겼다.



upload폴더하위에 정상적으로 이미지가 업로드되었다.



왼쪽 맨아래 '키' 라고 되있는 부분이 해당객체의 키값이다.



객체 Url을 웹브라우저 주소창에 입력후 정상적으로 나오면 끝!

S3 Url Pattern

http://버킷네임.s3.리전네임.amazonaws.com/객체이름



여기에 한가지 흠이있다면 accessKey, secretKey, bucket이 코드상으로 그대로 노출이 된다. 그래서 해당값들을 암호화를 해야하는데 관련링크는 예전에 정리해둔곳이 있는데 여길확인해보면 좋을 것 같다.


https://kim-jong-hyun.tistory.com/50

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함