생각정리/항해99

[주특기 프로젝트] 10일차

생각중임 2023. 9. 27. 00:31

이미지 업로드 S3 사용


1. 의존성 추가

S3와 이미지 파일을 받는 formdata를 사용하기 위한 의존성을 추가 해준다.

// amazon S3 설정을 사용하기 위한 의존성 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// formdata 이미지 파일을 받기 위한 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.2'
// formdata json형식 받기 위한 의존성 추가
implementation 'javax.xml.bind:jaxb-api:2.3.1'

2. amazon S3 config 설정

AmazonS3Client Bean은 기본적으로 사용할 수 있지만 나의 설정에 맞추기 위해서 새로 설정을 해준다.

@Configuration
public class AmazonS3Config {

    @Value("${aws.s3.access-key}")
    private String accessKey;

    @Value("${aws.s3.secret-key}")
    private String secretKey;

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

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .withRegion(region)
                .build();
    }
}

3. Controller 설정

@RequestPart를 이용해서 formdata를 받을 수 있다. 받을 때 MultipartFile의 형태로 이미지 파일을 받는다.

@PostMapping("/account/image")
public ResponseEntity<MessageResponseDto> userImageUpload(@RequestPart("image") List<MultipartFile> multipartFiles, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    return accountService.userImageUpload(multipartFiles, userDetails.getUser());
}

4. Image Entity 설정

유저 프로필을 기준으로 유저에 하나의 프로필 이미지로 관계 설정을 하여 사용을 하도록 하였다.

S3를 이용할 경로 path 변수와 S3에 올라간 이미지 파일을 사용하기 위한 Url를 변수로 사용한다.

@Getter
@NoArgsConstructor
@Entity
@Table(name = "user_image")
public class UserImage {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "image_path", nullable = false)
    private String imagePath;

    @Column(name = "image_url", nullable = false)
    private String imageUrl;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public UserImage(String imagePath, String imageUrl, User user) {
        this.imagePath = imagePath;
        this.imageUrl = imageUrl;
        this.user = user;
    }

    public void update(String imagePath, String imageUrl) {
        this.imagePath = imagePath;
        this.imageUrl = imageUrl;
    }
}

5. image repository

이미지 정보를 DB에 저장해서 관리하기 위해서 만들어 준다.

6. Service 설정

S3에 저장될 경로를 만들어주고 amazonS3Client를 이용해서 put을 시켜준다.
프로필은 유저와 1대1관계이기 때문에 신규로 생성해주고 데이터가 있다면 DB에서는 업데이트 S3에서는 기존에 파일은 삭제를 해서 관리를 하도록 했다.

@Transactional
public ResponseEntity<MessageResponseDto> userImageUpload(List<MultipartFile> multipartFiles, User user) {
    User selectUser = findUser(user.getUserId());

    String uploadFilePath = "userProfil";

    for (MultipartFile multipartFile : multipartFiles) {
        String originalFileName = multipartFile.getOriginalFilename();
        String uploadFileName = getUuidFileName(originalFileName);


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

        try (InputStream inputStream = multipartFile.getInputStream()) {
            String keyName = uploadFilePath + "/" + uploadFileName; // userProfil/파일.확장자

            // S3에 폴더 및 파일 업로드
            amazonS3Client.putObject(
                    new PutObjectRequest(bucketName, keyName, inputStream, objectMetadata)
            );

            // S3에 업로드한 폴더 및 파일 URL
            String uploadFileUrl = cloudFront + keyName;

            UserImage userImage = userImageRepository.findByUser(selectUser);
            if (userImage == null) {
                userImageRepository.save(new UserImage(keyName, uploadFileUrl, selectUser));
            } else {
                amazonS3Client.deleteObject(bucketName, userImage.getImagePath());
                userImage.update(keyName, uploadFileUrl);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    MessageResponseDto message = new MessageResponseDto("프로필 업로드 성공", HttpStatus.OK.value());
    return ResponseEntity.status(HttpStatus.OK).body(message);
}

// UUID 파일명 반환
private String getUuidFileName(String fileName) {
    String ext = fileName.substring(fileName.indexOf(".") + 1);
    return UUID.randomUUID().toString() + "." + ext;
}

7. amazon cloudfront 설정

S3를 엑세스 차단상태이기 때문에 S3안에 이미지를 불러오기 위해 cloudFront로 우회하여 사용한다.

원본 도메인은 만들어둔 S3를 선택해준다.

이름은 원하는 이름을 등록해 준다.