Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[INIT] S3 설정 #25

Merged
merged 3 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ dependencies {

// slack logback
implementation 'com.github.maricn:logback-slack-appender:1.4.0'

// s3
implementation("software.amazon.awssdk:bom:2.21.0")
implementation("software.amazon.awssdk:s3:2.21.0")
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.tiki.server.common.dto.BaseResponse;
import com.tiki.server.common.dto.ErrorResponse;
import com.tiki.server.document.exception.DocumentException;
import com.tiki.server.external.exception.ExternalException;
import com.tiki.server.member.exception.MemberException;
import com.tiki.server.memberteammanager.exception.MemberTeamManagerException;
import com.tiki.server.team.exception.TeamException;
Expand Down Expand Up @@ -53,4 +54,11 @@ public ResponseEntity<BaseResponse> documentException(DocumentException exceptio
val errorCode = exception.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
}

@ExceptionHandler(ExternalException.class)
public ResponseEntity<BaseResponse> externalException(ExternalException exception) {
log.error(exception.getMessage());
val errorCode = exception.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/tiki/server/external/config/AWSConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.tiki.server.external.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import lombok.RequiredArgsConstructor;
import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

@Configuration
@RequiredArgsConstructor
public class AWSConfig {

private static final String AWS_ACCESS_KEY_ID = "aws.accessKeyId";
private static final String AWS_SECRET_ACCESS_KEY = "aws.secretAccessKey";

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

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

@Value("${aws-property.aws-region}")
private String region;

@Bean
public SystemPropertyCredentialsProvider systemPropertyCredentialsProvider() {
System.setProperty(AWS_ACCESS_KEY_ID, accessKey);
System.setProperty(AWS_SECRET_ACCESS_KEY, secretKey);
return SystemPropertyCredentialsProvider.create();
}

@Bean
public S3Client getS3Client() {
return S3Client.builder()
.region(getRegion())
.credentialsProvider(systemPropertyCredentialsProvider())
.build();
}

@Bean
public S3Presigner getS3PreSigner() {
return S3Presigner.builder()
.region(getRegion())
.credentialsProvider(systemPropertyCredentialsProvider())
.build();
}

private Region getRegion() {
return Region.of(region);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.tiki.server.external.exception;

import com.tiki.server.external.message.ErrorCode;

import lombok.Getter;

@Getter
public class ExternalException extends RuntimeException {

private final ErrorCode errorCode;

public ExternalException(ErrorCode errorCode) {
super("[ExternalException] : " + errorCode.getMessage());
this.errorCode = errorCode;
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/tiki/server/external/message/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.tiki.server.external.message;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

import org.springframework.http.HttpStatus;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode {

/* 400 BAD_REQUEST : 잘못된 요청 */
INVALID_FILE_SIZE(BAD_REQUEST, "파일의 용량은 30MB를 초과할 수 없습니다.");

private final HttpStatus httpStatus;
private final String message;
}
84 changes: 84 additions & 0 deletions src/main/java/com/tiki/server/external/util/S3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.tiki.server.external.util;

import static com.tiki.server.external.message.ErrorCode.*;

import java.io.IOException;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.tiki.server.external.config.AWSConfig;
import com.tiki.server.external.exception.ExternalException;
import com.tiki.server.external.message.ErrorCode;

import lombok.RequiredArgsConstructor;
import lombok.val;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

@Component
@RequiredArgsConstructor
public class S3Service {

private static final Long MAX_FILE_SIZE = 30 * 1024 * 1024L;
private static final String FILE_NAME_START_POINT = ".";
private static final String CONTENT_DISPOSITION = "inline";
private static final String DELIMITER = "/";

private final AWSConfig awsConfig;

@Value("${aws-property.bucket}")
private String bucket;

@Value("${aws-property.s3-url}")
private String s3URL;

public String uploadFile(String directoryPath, MultipartFile file) {
validateFileSize(file);
try {
val key = directoryPath + DELIMITER + generateFileName(file);
val s3Client = awsConfig.getS3Client();
val request = createRequest(key, file.getContentType());
val requestBody = RequestBody.fromBytes(file.getBytes());
s3Client.putObject(request, requestBody);
return s3URL + key;
} catch (IOException exception) {
throw new IllegalArgumentException();
}
}

public void deleteFile(String key) throws IOException {
val s3Client = awsConfig.getS3Client();
s3Client.deleteObject((DeleteObjectRequest.Builder builder) ->
builder.bucket(bucket)
.key(key)
.build()
);
}

private PutObjectRequest createRequest(String key, String contentType) {
return PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(contentType)
.contentDisposition(CONTENT_DISPOSITION)
.build();
}

private String generateFileName(MultipartFile file) {
return UUID.randomUUID() + getFileFormat(file.getName());
}

private String getFileFormat(String fileName) {
return fileName.substring(fileName.lastIndexOf(FILE_NAME_START_POINT));
}

private void validateFileSize(MultipartFile file) {
if (file.getSize() > MAX_FILE_SIZE) {
throw new ExternalException(INVALID_FILE_SIZE);
}
}
}
9 changes: 8 additions & 1 deletion src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ logging:
org.hibernate.SQL: debug
slack:
webhook_url: ${SLACK.WEBHOOK_URL.dev}
config: classpath:logback-spring.xml
config: classpath:logback-spring.xml

aws-property:
access-key: ${AWS_PROPERTY.ACCESS_KEY}
secret-key: ${AWS_PROPERTY.SECRET_KEY}
bucket: ${AWS_PROPERTY.BUCKET}
aws-region: ap-northeast-2
s3-url: ${AWS_PROPERTY.S3_URL}
Loading