관리 메뉴

개발하는 동그리

[Main Project] AWS S3 사진 업로드 설정 본문

스테이츠 코드(백엔드)/Main Project

[Main Project] AWS S3 사진 업로드 설정

개발하는 동그리 2022. 9. 29. 10:21
반응형

Application.yml

spring:
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 20MB
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
    username:
    password:
    driver-class-name: org.h2.Driver


  jpa:
    hibernate:
      ddl-auto: create  # (1) ??? ?? ??
    show-sql: true      # (2) SQL ?? ??
    properties:
      hibernate:
        format_sql: true  # (3) SQL pretty print


logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG


cloud:
  aws:
    credentials:
      accessKey: { }
      secretKey: { }
    s3:
      bucket: { bucket name }
    region:
      static: "ap-northeast-2"
    stack:
      auto: false

 

AWSConfig

@Configuration
public class AWSConfig {

    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")
    private String accessSecret;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public AmazonS3 s3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, accessSecret);
        return AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region).build();
    }
}

 

AwsS3Service

@Transactional
@RequiredArgsConstructor
@Slf4j
@Service
public class AwsS3Service {

    private final AmazonS3Client amazonS3Client;

    private final ReviewImgRepository reviewImgRepository;

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

    /**
     * 단일 파일 업로드
     */

    public String singleUploadFile(MultipartFile multipartFile) {

        validateFileExists(multipartFile);

        String fileName = originalFileName(multipartFile);

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

        try (InputStream inputStream = multipartFile.getInputStream()) {

            amazonS3Client.putObject(new PutObjectRequest
            (bucketName, fileName, inputStream, objectMetadata)
                    .withCannedAcl(CannedAccessControlList.PublicRead));

        } catch (IOException e) {
            log.error("Can Not Upload Image..!", e);
            throw new RuntimeException();
        }
        return amazonS3Client.getUrl(bucketName, fileName).toString();
    }

    //s3로 파일 업로드 하고 url 리턴하는 메서드

    public String uploadFile(MultipartFile multipartFile) throws IOException {
        validateFileExists(multipartFile);

        String fileName = originalFileName(multipartFile);

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());
        try (InputStream inputStream = multipartFile.getInputStream()) {
            byte[] bytes = IOUtils.toByteArray(inputStream);
            objectMetadata.setContentLength(bytes.length);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            amazonS3Client.putObject(new PutObjectRequest
            (bucketName, fileName, byteArrayInputStream, objectMetadata)
                    .withCannedAcl(CannedAccessControlList.PublicRead));
        } catch (Exception e) {
            log.error("Can not upload image, ", e);
            throw new FileUploadException();
        }
        String url = amazonS3Client.getUrl(bucketName, fileName).toString();

        return url;
    }

    //파일 이름 추출하는 메서드
    public String originalFileName(MultipartFile multipartFile) {
        try {
            return CommonUtils.buildFileName(multipartFile.getOriginalFilename());
        } catch (Exception e) {
            log.error("Can not filename, ", e);
            return "filename";
        }

    }

    // review 이미지 업로드

    public List<ReviewImg> convertReviewImg(List<MultipartFile> multipartFileList) {
        if (multipartFileList.size() > 3) throw new RuntimeException("사진 파일 갯수 초과");

        List<ReviewImg> fileList = new ArrayList<>();
        multipartFileList.forEach(file -> {
            String url;
            try {
                url = uploadFile(file);
            } catch (IOException e) {
                log.error("Can not upload, ", e);
                throw new RuntimeException(e);
            }
            String fileName = originalFileName(file);

            ReviewImg reviewImg = ReviewImg.builder()
                    .fileName(fileName)
                    .imgUrl(url)
                    .build();

            reviewImgRepository.save(reviewImg);

            fileList.add(reviewImg);
        });

        return fileList;
    }

    // review 이미지 업데이트

    public List<ReviewImg> updateReviewImg
    (List<ReviewImg> reviewImgList, List<MultipartFile> multipartFileList) {
        if (multipartFileList.size() > 3) throw new RuntimeException("사진 파일 갯수 초과");

        // 기존에 있있던 파일이름 불러오기
        List<String> list = reviewImgList.stream()
        .map(ReviewImg::getFileName).collect(Collectors.toList());
        for (String file : list) {
            if (!"".equals(file) && file != null) {
                boolean isExistObject = amazonS3Client.doesObjectExist(bucketName, file);

                // 파일이 있으면 삭제
                if (isExistObject) amazonS3Client.deleteObject(bucketName, file);
            }
        }

        // 삭제후 새로운 이미지 업로드
        return convertReviewImg(multipartFileList);
    }

    //file 이름과 url 받아서 List<PostsImg> 로 리턴하는 메서드

    public List<PostsImg> convertPostImg(List<MultipartFile> multipartFileList) {
        if (multipartFileList.size() != 5) {
            throw new RuntimeException("you need to upload 5 image");
        }
        List<PostsImg> fileList = new ArrayList<>();

        multipartFileList.forEach(file -> {

            String url;
            try {
                url = uploadFile(file);
            } catch (IOException e) {
                log.error("Can not upload, ", e);
                throw new RuntimeException(e);
            }
            String fileName = originalFileName(file);

            PostsImg postsImg = PostsImg.builder()
                    .fileName(fileName)
                    .imgUrl(url)
                    .build();
            fileList.add(postsImg);
        });
        return fileList;
    }

    //List<PostsImg> 수정 메서드(기존 s3에 있는 파일 삭제 후 추가) 
    // s3에 기존 파일이 삭제되지 않는 에러가 있음 추후 수정 예정
    
    public List<PostsImg> reviseFileV1(List<PostsImg> imgList
    								,List<MultipartFile> multipartFileList) {
        List<String> list = imgList.stream().map(PostsImg::getFileName)
        .collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) { //기존 파일 삭제
            String currentFilePath = list.get(i);
            if (!"".equals(currentFilePath) && currentFilePath != null) {
                boolean isExistObject = 
                amazonS3Client.doesObjectExist(bucketName, currentFilePath);

                if (isExistObject) {
                    amazonS3Client.deleteObject(bucketName, currentFilePath);
                }
            }
        }
        return convertPostImg(multipartFileList);
    }

    //s3에 업로드 할 사진 validation 메서드
    private void validateFileExists(MultipartFile multipartFile) {
        if (multipartFile.isEmpty()) {
            throw new RuntimeException("EmptyFileException()");
        }
    }
}

 

CommonUtils

public class CommonUtils {
    private static final String FILE_EXTENSION_SEPARATOR = ".";

    public static String buildFileName(String originalFileName) {
        int fileExtensionIndex = originalFileName.lastIndexOf(FILE_EXTENSION_SEPARATOR);
        String fileExtension = originalFileName.substring(fileExtensionIndex);
        String fileName = originalFileName.substring(0, fileExtensionIndex);
        String now = String.valueOf(System.currentTimeMillis());

        return fileName + "_" + now + fileExtension;
    }
}

 

Controller Class

@PostMapping("/create")
public ResponseEntity createCard(@RequestPart(value = "dogCardDto") DogCardDto.Post dogCardDto,
                                 @RequestPart(value = "file") MultipartFile file,
                                 @AuthenticationPrincipal PrincipalDetails principalDetails) {

    DogCard dogCard = mapper.dogDtoToDogCard(dogCardDto);

    dogCardService.savedDogCard(dogCard, file, principalDetails.getUsers());

    log.info("dogName = {}", dogCard.getDogName());
    log.info("dogType = {}", dogCard.getType());
    log.info("dogImageUrl = {}", dogCard.getPhotoImgUrl());

    return new ResponseEntity<>(new SingleResponseDto<>("create success"), HttpStatus.CREATED);
}

 

AwsConfig, AwsS3Service, CommonUtils, application.yml 설정 완료 후

Controller Class 이미지 업로드 설정
@RequestPart(value = "file" ) MultipartFile file

이미지 파일과 동시에 json data를 올려야 할 때
@RequestPart(value = "dto") PostDto postDto와 같이 입력해주면 된다. 

※ 주의할 점은 value 의 file & Multifile의 file의 값이 일치해야 한다.  

반응형