Skip to content

Commit

Permalink
feat: s3 관련 코드 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
ss7622 committed Nov 22, 2024
1 parent 80cb3c5 commit d2699e4
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ jobs:
echo "DB_USERNAME=${{ secrets.DB_USERNAME }}" >> ~/deploy/.env
echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> ~/deploy/.env
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }}" >> ~/deploy/.env
echo "ACCESS_KEY=${{ secrets.ACCESS_KEY }}" >> ~/deploy/.env
echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> ~/deploy/.env
echo "BUCKET_NAME=${{ secrets.BUCKET_NAME }}" >> ~/deploy/.env
echo "REGION=${{ secrets.REGION }}" >> ~/deploy/.env
sudo docker-compose -f ~/deploy/docker-compose.yml pull
sudo docker-compose -f ~/deploy/docker-compose.yml up -d
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ dependencies {
//database
runtimeOnly 'org.postgresql:postgresql'

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

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ services:
DB_URL: ${DB_URL}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
# ACCESS_KEY: ${ACCESS_KEY}
# SECRET_KEY: ${SECRET_KEY}
# BUCKET_NAME: ${BUCKET_NAME}
# REGION: ${REGION}
ACCESS_KEY: ${ACCESS_KEY}
SECRET_KEY: ${SECRET_KEY}
BUCKET_NAME: ${BUCKET_NAME}
REGION: ${REGION}
# JWT_SECRET_KEY: ${JWT_SECRET_KEY}
# MAIL_USERNAME : ${MAIL_USERNAME}
# MAIL_PASSWORD : ${MAIL_PASSWORD}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.hackathon.global.exception;

import lombok.Getter;

@Getter
public class BaseErrorException extends RuntimeException {

private final int ErrorCode;

public BaseErrorException(int errorCode, String message) {
super(message);
ErrorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.example.hackathon.global.exception;


import com.example.hackathon.global.exception.Response.ExceptionResponse;
import java.io.UnsupportedEncodingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

// 로그 형식
private static final String LOG_FORMAT = "Class : {}, Code : {}, Message : {}";
private static final int ERROR_CODE = 400;
private static final int SERVER_ERROR_CODE = 500;

// 사용자 정의 예외 처리
@ExceptionHandler(BaseErrorException.class)
public ResponseEntity<ExceptionResponse<Void>> handle(BaseErrorException e) {

logWarning(e, e.getErrorCode());
ExceptionResponse<Void> response = ExceptionResponse.fail(e.getErrorCode(), e.getMessage());

return ResponseEntity
.status(e.getErrorCode())
.body(response);
}

// @Valid 예외 처리 (@NotNull, @Size, etc...) or IllegalArgumentException
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionResponse<Void>> handle(MethodArgumentNotValidException e) {

logWarning(e, ERROR_CODE);
ExceptionResponse<Void> response = ExceptionResponse.fail(ERROR_CODE, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());

return ResponseEntity
.status(ERROR_CODE)
.body(response);
}

@ExceptionHandler({UnsupportedEncodingException.class})
public ResponseEntity<ExceptionResponse<Void>> handle(UnsupportedEncodingException e) {


logWarning(e, ERROR_CODE);
ExceptionResponse<Void> response = ExceptionResponse.fail(ERROR_CODE, e.getMessage());

return ResponseEntity
.status(ERROR_CODE)
.body(response);
}

// 서버 측 에러 (이외의 에러)
@ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionResponse<Void>> handle(Exception e) {

logWarning(e, SERVER_ERROR_CODE);
ExceptionResponse<Void> response = ExceptionResponse.fail(SERVER_ERROR_CODE, e.getMessage());

return ResponseEntity
.status(SERVER_ERROR_CODE)
.body(response); }

// log.warn이 중복되어 리팩토링
private void logWarning(Exception e, int errorCode) {
log.warn(e.getMessage(), e); // 전체 로그 출력, 운영 단계에서는 삭제
log.warn(LOG_FORMAT, e.getClass().getSimpleName(), errorCode, e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.hackathon.global.exception.Response;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ExceptionResponse<T> {

private int code;
private String message;
private T data;

private ExceptionResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

private ExceptionResponse(int code, String message) {
this.code = code;
this.message = message;
}

public static <T> ExceptionResponse<T> fail(int code, String message) {

return new ExceptionResponse<>(code, message);
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/example/hackathon/global/s3/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.hackathon.global.s3.config;

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 S3Config {

@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 AmazonS3 amazonS3Client(){
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

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


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.hackathon.global.s3.exception;

import com.example.hackathon.global.exception.BaseErrorException;

public abstract class S3ErrorException {

public static class S3UploadFailException extends BaseErrorException {
public S3UploadFailException() {
super(S3ErrorMessage.UPLOAD_FAIL.getErrorCode(), S3ErrorMessage.UPLOAD_FAIL.getMessage());
}
}

public static class S3NotExistNameException extends BaseErrorException {
public S3NotExistNameException() {
super(S3ErrorMessage.NOT_EXIST_NAME.getErrorCode(), S3ErrorMessage.NOT_EXIST_NAME.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.hackathon.global.s3.exception;

import lombok.Getter;

@Getter
public enum S3ErrorMessage {

UPLOAD_FAIL(400, "사진 업로드에 실패하였습니다."),
NOT_EXIST_NAME(404, "존재하지 않는 파일 이름입니다.");

private final int errorCode;
private final String message;

S3ErrorMessage(int errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.example.hackathon.global.s3.service;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.example.hackathon.global.s3.exception.S3ErrorException.S3NotExistNameException;
import com.example.hackathon.global.s3.exception.S3ErrorException.S3UploadFailException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class S3Service {

private final AmazonS3 s3Client;
private final String bucketName;

public S3Service(AmazonS3 s3Client, @Value("${bucket_name}") String bucketName) {
this.s3Client = s3Client;
this.bucketName = bucketName;
}

public URL uploadImages(MultipartFile file) {
String key = file.getOriginalFilename();
checkExistFile(key);
// MultipartFile에서 InputStream을 얻어 S3에 업로드합니다.
try (InputStream inputStream = file.getInputStream()) {
ObjectMetadata metadata = setMetaData(file);

// PutObjectRequest 생성 시 InputStream과 ContentType을 설정합니다.
PutObjectRequest putRequest = new PutObjectRequest(bucketName, key, inputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead);
s3Client.putObject(putRequest);
return getImageUrl(key);
} catch (IOException e) {
throw new S3UploadFailException();
}
}

public URL getImageUrl(String key) {
try {
return s3Client.getUrl(bucketName, key);
} catch (Exception e) {
throw new S3NotExistNameException();
}
}

public void deleteImage(String key) {
try {
s3Client.deleteObject(bucketName, key);
} catch (Exception e) {
throw new S3NotExistNameException();
}
}

private ObjectMetadata setMetaData(MultipartFile file) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());

return metadata;
}

private void checkExistFile(String key) {
if (s3Client.doesObjectExist(bucketName, key)) {
s3Client.deleteObject(bucketName, key);
}
}

}
12 changes: 11 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ spring:
properties:
hibernate:
# show_sql: true
format_sql: true
format_sql: true

cloud:
aws:
credentials:
access-key: ${ACCESS_KEY}
secret-key: ${SECRET_KEY}
s3:
bucket: ${BUCKET_NAME}
region:
static: ${REGION}

0 comments on commit d2699e4

Please sign in to comment.