반응형

1. AWS S3 생성

1-1 버킷 생성

AWS 사이트에 들어가 로그인을 해준 후 S3탭에 들어가서 [버킷 만들기] 버튼을 누른다. 설정은 위의 설정만 똑같이 해주고 다른 설정은 건들 필요 없이 [버킷 만들기] 버튼을 눌러준다.

 

 

 

1-2 버킷 생성 확인

위와 같이 버킷이 생성이 되었다면 [다음]으로 넘어간다.

 

 

 

 

 

2. IAM 설정

2-1 사용자 생성

 

IAM을 검색해서 들어간후 [사용자 생성] 버튼을 누른다.

 

 

 

사용할 이름만 작성 후 [다음]으로 넘어간다.

 

 

 

다음 버튼을 누르면 이 화면이 뜬다. 세 번째 직접 정책 연결 버튼을 누른 뒤, 검색 창에 s3 full을 쳐서 AmazonS3 FullAccess 버튼을 체크 후 [다음]으로 넘어간다.

 

 

 

[사용자 생성]을 눌러 주면 된다.

 

 

 

 

 

2-2 액세스키, 시크릿키 저장

2-1을 잘 따라 했다면 다음과 같이 사용자가 생성된다. 파란색으로 된 사용자 이름을 클릭해 준다.

 

 

 

클릭을 해주고 보안 자격 증명 탭에 들어가면 [액세스 키 만들기] 버튼이 보인다. 클릭해 준다.

 

 

 

위와 같이 체크해 주고 다음 버튼을 누른다.

 

 

 

태그 설정 후 [액세스 키 만들기] 버튼을 클릭해 준다.

 

 

 

만들어졌다면 액세스키, 비밀 액세스키를 따로 저장해 두고 csv 파일을 다운로드해둔다.

 

 

 

3. 프로젝트에 적용하기

3-1 의존성 추가

    //aws s3
    implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

build.gradle에 위의 의존성을 추가해 준다.

 

 

3-2 application/properties 추가

cloud.aws.credentials.accessKey= //발급받은 엑세스키
cloud.aws.credentials.secretKey= //발급받은 시크릿키
cloud.aws.s3.bucket= //버킷이름
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto-=false

yml 파일 또는 properties에 다음과 같은 설정을 작성해 준다. (aws 키값은 노출이 되면 곤란해질 수 있으니 주의하고 yml, properties 파일은 /gitignore에 넣어서 git에 업로드 되게 하지 않는다.)

 

 

 

3-3 config 추가

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

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

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

    @Bean
    public AmazonS3 amazonS3Client(){
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region)
                .build();
    }
}

다음과 같이 config 파일을 추가해 준다. AmazonS3 객체를 만들어 주입해 주는 과정이다.

 

 

 

3-4 S3 Uploader 추가

@RequiredArgsConstructor
@Service
@Component
@Slf4j
public class S3Uploader {
    private final AmazonS3 amazonS3;

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

    public String upload(MultipartFile multipartFile, String dirName) throws IOException {
        File uploadFile = convert(multipartFile)
                .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
        return uploadFile(uploadFile, dirName);
    }

    private String uploadFile(File uploadFile, String dirName) {
        String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName();
        String uploadImageUrl = putS3(uploadFile, fileName);

        removeNewFile(uploadFile);  // 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)

        return uploadImageUrl;      // 업로드된 파일의 S3 URL 주소 반환
    }

    private String putS3(File uploadFile, String fileName) {
        amazonS3.putObject(
                new PutObjectRequest(bucket, fileName, uploadFile)
                        .withCannedAcl(CannedAccessControlList.PublicRead)    // PublicRead 권한으로 업로드 됨
        );
        return amazonS3.getUrl(bucket, fileName).toString();
    }

    private void removeNewFile(File targetFile) {
        if (targetFile.delete()) {
            log.info("파일 삭제가 완료되었습니다.");
        } else {
            log.info("파일 삭제가 실패되었습니다.");
        }
    }

    private Optional<File> convert(MultipartFile files) throws IOException {
        // stack-over-flow code
        File convertFile = new File(System.getProperty("java.io.tmpdir") +
                System.getProperty("file.separator" ) +
                files.getOriginalFilename());

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

    //file 삭제

    public void fileDelete(String s3Key) {
        try {
            amazonS3.deleteObject(this.bucket, s3Key);
        } catch (AmazonServiceException e) {
            System.out.println(e.getErrorMessage());
        }
    }

파일을 받아 서버로 업로드하는 코드이다.

 

각 메서드 역할

1. upload: MultipartFile을 받아서 파일을 로컬 시스템에 저장한 후, 해당 파일을 Amazon S3로 업로드. 업로드된 파일의 URL을 반환.


2. uploadFile: 업로드할 파일과 파일 이름을 받아서 S3에 업로드하고, 업로드된 파일의 URL을 반환.


3. putS3: 실제로 파일을 S3에 업로드. PutObjectRequest를 생성하여 Amazon S3에 파일을 업로드하고, 업로드된 파일의 URL을 반환.


4. removeNewFile: 로컬에 생성된 파일을 삭제. S3에 업로드한 후에는 더 이상 로컬에 저장된 파일이 필요하지 않으므로 해당 파일을 삭제하는 역할.


5. convert: MultipartFile을 File로 변환. MultipartFile을 로컬 시스템의 임시 디렉터리에 저장하고, 이를 File 객체로 변환하여 반환.


6. fileDelete: S3에서 파일을 삭제. S3에서 지정된 키(s3 Key)에 해당하는 파일을 삭제하는 역할.

 

 

 

 

 

4. 용도에 따라 적용시키기 (게시글에 이미지 추가 / 프로필 사진 추가) - 프로필 사진 추가법

4-1 profile_url 칼럼 추가

    @Column(name = "profile_url")
    private String profileUrl = "/images/logo.png";

나는 웹사이트의 로고 이미지를 기본 프로필로 사용하기 위해 User Entity에 다음과 같은 칼럼을 생성해 줬다.

 

 

4-2 컨트롤러 작성(업로드, 파일삭제)

   /**
     * 프로필 사진 업로드(변경)
     * @param file 이미지 파일
     * @param userDetails 로그인 정보
     * @return 결과 반환
     * @throws IOException
     */
    @PostMapping("/mypage/profile/upload")
    public ResponseEntity<String> uploadProfileImage(@RequestParam("profileimage")MultipartFile file,
                                                     @AuthenticationPrincipal UserDetailsImpl userDetails) throws IOException {
        String result = myPageService.uploadProfileImage(file, userDetails.getUser());
        return ResponseEntity.ok(result);
    }
    
    

    /**
     * 프로필 사진 제거(기본 이미지로 변경)
     * @param userDetails 로그인 정보
     * @return 결과 반환
     */
    @DeleteMapping("/mypage/profile/delete")
    public ResponseEntity<String> deleteProfileImage(@AuthenticationPrincipal UserDetailsImpl userDetails){
        String result = myPageService.deleteProfileImage(userDetails.getUser());
        return ResponseEntity.ok(result);
    }

우선 두 개의 메서드만 작성해서 사용했다. 프로필 사진을 업로드하는 것과 제거하는 것 두 개를 만들었다.

프로필 변경을 할 때는 이미지 파일, 로그인 정보 두가지를 받도록 했고, 사진 제거를 할때는 로그인된 유저 정보만 받아왔다.

 

 

 

4-3 서비스 코드 작성

   @Transactional
    public String uploadProfileImage(MultipartFile file, User user) throws IOException {
        User requestUser = userRepository.findById(user.getId()).orElseThrow(
                () -> new IllegalArgumentException("로그인 후 사용하세요.")
        );

        String ProfileUrl = s3Uploader.upload(file, "profile");
        requestUser.setProfileUrl(ProfileUrl);

        return "프로필 사진 업로드에 성공하였습니다.";
    }


    @Transactional
    public String deleteProfileImage(User user) {
        User requestUser = userRepository.findById(user.getId()).orElseThrow(
                () -> new IllegalArgumentException("로그인 후 사용하세요.")
        );

        String s3Url = user.getProfileUrl();
        String s3Key = s3Url.substring(s3Url.indexOf("profile/"));

        s3Uploader.fileDelete(s3Key);
        requestUser.setProfileUrl("/images/logo.png");

        return "프로필 사진 제거에 성공했습니다.";
    }

uploadProfileImage - 로그인 정보를 받아와 User 객체를 만들어주고 넘겨받은 파일을 s3에 업로드 해준 뒤 setProfileUrl을 사용해서 반환받은 사진 Url을 설정해 줬다. 

 

deleteProfileImage - 로그인 정보를 받아와 User 객체를 만들어주고 저장되어 있는 프로필 url을 가지고 온다. 그 뒤 key값 형태에 맞추기 위해 s3 Url을 profile/부터 잘라서 저장했다. 그 뒤 fileDelete를 사용해 객체를 제거하고 프로필 사진을 기본 로고로 바꿔줬다.

 

 

 

4-4 테스트

기본 이미지 사진.

간단한 테스트를 위해 먼저 프런트 부분에서 사진을 띄워서 사용했다.

 

 

포스트맨을 사용해서 테스트했다.

 

 

 

변경 후 바뀐 이미지 사진.

반응형

+ Recent posts