Skip to content

Commit

Permalink
Merge pull request #7 from Fairy-Taless/feature/#5-s3
Browse files Browse the repository at this point in the history
✨ FEAT. S3 연동
  • Loading branch information
junhaa authored Apr 8, 2024
2 parents 3a89ab9 + ff1a3b4 commit 0cb0c21
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 0 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

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

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/fairytale/tbd/global/aws/s3/AmazonS3Manager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fairytale.tbd.global.aws.s3;

import java.io.IOException;

import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;

import fairytale.tbd.global.config.AmazonConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class AmazonS3Manager {

private final AmazonS3 amazonS3;
private final AmazonConfig amazonConfig;

public String uploadFile(String keyName, MultipartFile file) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
try {
PutObjectResult putObjectResult = amazonS3.putObject(
new PutObjectRequest(amazonConfig.getBucket(), keyName, file.getInputStream(), metadata));
log.info("result={}", putObjectResult.getContentMd5());
} catch (IOException e) {
log.error("error at AmazonS3Manager uploadFile : {}", (Object)e.getStackTrace());
}

return amazonS3.getUrl(amazonConfig.getBucket(), keyName).toString();
}

}
56 changes: 56 additions & 0 deletions src/main/java/fairytale/tbd/global/config/AmazonConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package fairytale.tbd.global.config;

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

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;

import jakarta.annotation.PostConstruct;
import lombok.Getter;

@Configuration
@Getter
public class AmazonConfig {

private AWSCredentials awsCredentials;

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

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

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

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



@PostConstruct
public void init(){
this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
}

@Bean
public AmazonS3 amazonS3(){
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}

@Bean
public AWSCredentialsProvider awsCredentialsProvider(){
return new AWSStaticCredentialsProvider(awsCredentials);
}

}
22 changes: 22 additions & 0 deletions src/main/java/fairytale/tbd/global/entity/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fairytale.tbd.global.entity;

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;
}
13 changes: 13 additions & 0 deletions src/main/java/fairytale/tbd/global/enums/statuscode/BaseCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fairytale.tbd.global.enums.statuscode;

import org.springframework.http.HttpStatus;

public interface BaseCode {
String getCode();

String getMessage();

HttpStatus getHttpStatus();

Integer getStatusValue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package fairytale.tbd.global.enums.statuscode;

import org.springframework.http.HttpStatus;

public enum ErrorStatus implements BaseCode{
// common
_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."),
_BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."),
_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."),
_FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."),


// ElevenLabs
_FILE_CONVERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FILE5001", "파일 변환에 실패했습니다.");


private final HttpStatus httpStatus;
private final String code;
private final String message;

ErrorStatus(HttpStatus httpStatus, String code, String message) {
this.httpStatus = httpStatus;
this.code = code;
this.message = message;
}

@Override
public String getCode() {
return code;
}

@Override
public String getMessage() {
return message;
}

@Override
public HttpStatus getHttpStatus() {
return httpStatus;
}

@Override
public Integer getStatusValue() {
return httpStatus.value();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fairytale.tbd.global.enums.statuscode;

import org.springframework.http.HttpStatus;

public enum SuccessStatus implements BaseCode{
_OK(HttpStatus.OK, "COMMON200", "성공입니다."),
_ACCEPTED(HttpStatus.ACCEPTED, "COMMON204", "별도의 응답 데이터가 없으며, 정상 처리되었습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;

SuccessStatus(HttpStatus httpStatus, String code, String message) {
this.httpStatus = httpStatus;
this.code = code;
this.message = message;
}

@Override
public String getCode() {
return code;
}

@Override
public String getMessage() {
return message;
}

@Override
public HttpStatus getHttpStatus() {
return httpStatus;
}

@Override
public Integer getStatusValue() {
return httpStatus.value();
}
}
84 changes: 84 additions & 0 deletions src/main/java/fairytale/tbd/global/exception/ExceptionAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package fairytale.tbd.global.exception;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
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;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import fairytale.tbd.global.enums.statuscode.ErrorStatus;
import fairytale.tbd.global.response.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = GeneralException.class)
public ResponseEntity<Object> handleGeneralException(GeneralException exception, HttpServletRequest request) {
return handleExceptionInternal(exception, HttpHeaders.EMPTY, request);
}

@Override
public ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatusCode status, WebRequest request) {

log.info("handleGeneralException 발생 ={}", exception);
Map<String, String> errors = new LinkedHashMap<>();
exception.getBindingResult().getFieldErrors().stream()
.forEach(fieldError -> {

String fieldName = fieldError.getField();
String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
errors.merge(fieldName, errorMessage,
(existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
});

exception.getBindingResult().getGlobalErrors().stream()
.forEach(globalError -> {
log.info("globalError = {}", globalError);
String objectName = globalError.getObjectName();
String errorMessage = Optional.ofNullable(globalError.getDefaultMessage()).orElse("");
errors.merge(objectName, errorMessage,
(existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
});

return handleExceptionInternalArgs(exception, HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"), request,
errors);
}

@ExceptionHandler
public ResponseEntity<Object> handlingException(Exception exception, WebRequest request) {
return handleExceptionInternal(exception, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY,
ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(), request, exception.getMessage());
}

private ResponseEntity<Object> handleExceptionInternal(GeneralException exception, HttpHeaders headers,
HttpServletRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(exception.getErrorCode(), exception.getErrorReason(), null);
WebRequest webRequest = new ServletWebRequest(request);
return super.handleExceptionInternal(exception, body, headers, exception.getHttpStatus(), webRequest);
}

private ResponseEntity<Object> handleExceptionInternal(Exception exception, ErrorStatus errorStatus,
HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) {
ApiResponse<String> body = ApiResponse.onFailure(errorStatus.getCode(), errorStatus.getMessage(), errorPoint);
return super.handleExceptionInternal(exception, body, headers, status, request);
}

private ResponseEntity<Object> handleExceptionInternalArgs(Exception e, HttpHeaders headers,
ErrorStatus errorCommonStatus, WebRequest request, Map<String, String> errorArgs) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(),
errorArgs);
return super.handleExceptionInternal(e, body, headers, errorCommonStatus.getHttpStatus(), request);
}
}
23 changes: 23 additions & 0 deletions src/main/java/fairytale/tbd/global/exception/GeneralException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fairytale.tbd.global.exception;

import org.springframework.http.HttpStatus;

import fairytale.tbd.global.enums.statuscode.BaseCode;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class GeneralException extends RuntimeException{
private final BaseCode errorStatus;

public String getErrorCode() {
return errorStatus.getCode();
}

public String getErrorReason() {
return errorStatus.getMessage();
}

public HttpStatus getHttpStatus() {
return errorStatus.getHttpStatus();
}
}
35 changes: 35 additions & 0 deletions src/main/java/fairytale/tbd/global/response/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package fairytale.tbd.global.response;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import fairytale.tbd.global.enums.statuscode.BaseCode;
import fairytale.tbd.global.enums.statuscode.SuccessStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {

private final Boolean isSuccess;
private final String code;

private final String message;
private T result;

// 성공한 경우 응답 생성

public static <T> ApiResponse<T> onSuccess(T result) {
return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result);
}

public static <T> ApiResponse<T> of(boolean isSuccess, BaseCode code, T result) {
return new ApiResponse<>(isSuccess, code.getCode(), code.getMessage(), result);
}

// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(String code, String message, T data) {
return new ApiResponse<>(false, code, message, data);
}
}

0 comments on commit 0cb0c21

Please sign in to comment.