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

[REFACTOR] CDN+ 적용 #28

Merged
merged 3 commits into from
Sep 21, 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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public class NCPStorageService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;

@Value("${cloud.aws.cdn.domain}")
private String cdnDomain;

public String uploadFile(MultipartFile multipartFile, String path) {
// UUID를 사용하여 파일 이름 생성
String fileName = UUID.randomUUID() + "_" + Objects.requireNonNull(multipartFile.getOriginalFilename());
Expand All @@ -40,20 +43,22 @@ public String uploadFile(MultipartFile multipartFile, String path) {
amazonS3.putObject(new PutObjectRequest(bucket, fileLocation, multipartFile.getInputStream(), metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));

return amazonS3.getUrl(bucket, fileLocation).toString();
return cdnDomain + "/" + fileLocation;
} catch (IOException e) {
throw new FileConvertFailException(GlobalErrorCode.FILE_CONVERT_FAIL);
}
}

public void deleteFile(String fileUrl) {
// URL에서 파일 이름 추출
String fileName = fileUrl.substring(fileUrl.indexOf(bucket) + bucket.length() + 1);
String fileName = fileUrl.substring(cdnDomain.length() + 1);

log.info("fileName: {}", fileName);

try {
amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName));
} catch (Exception e) {
log.error("파일 삭제 실패: {}", e.getMessage());
// 필요 시 적절한 예외 처리 추가
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,57 @@
import com.shwimping.be.global.exception.response.ErrorResponse.ValidationError;
import com.shwimping.be.global.exception.response.ErrorResponse.ValidationErrors;
import com.shwimping.be.place.exception.PlaceNotFoundException;
import com.shwimping.be.review.exception.CanNotDeleteReviewException;
import com.shwimping.be.review.exception.ReviewNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;

import com.shwimping.be.user.exception.InvalidEmailException;
import com.shwimping.be.user.exception.InvalidPasswordException;
import com.shwimping.be.user.exception.UserNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.BindException;
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.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

private static final Logger log = LoggerFactory.getLogger("ErrorLogger");
private static final String LOG_FORMAT_INFO = "\n[🔵INFO] - ({} {})\n(id: {}, role: {})\n{}\n {}: {}";
private static final String LOG_FORMAT_ERROR = "\n[🔴ERROR] - ({} {})\n(id: {}, role: {})";

@ExceptionHandler(PlaceNotFoundException.class)
public ResponseEntity<Object> handlePlaceNotFound(PlaceNotFoundException e) {
public ResponseEntity<Object> handlePlaceNotFound(PlaceNotFoundException e, HttpServletRequest request) {
logInfo(e.getErrorCode(), e, request);
return handleExceptionInternal(e.getErrorCode());
}

@ExceptionHandler(CanNotDeleteReviewException.class)
public ResponseEntity<Object> handleCanNotDeleteReview(CanNotDeleteReviewException e, HttpServletRequest request) {
logInfo(e.getErrorCode(), e, request);
return handleExceptionInternal(e.getErrorCode());
}

@ExceptionHandler(ReviewNotFoundException.class)
public ResponseEntity<Object> handleReviewNotFound(ReviewNotFoundException e, HttpServletRequest request) {
logInfo(e.getErrorCode(), e, request);
return handleExceptionInternal(e.getErrorCode());
}

@ExceptionHandler(FileConvertFailException.class)
public ResponseEntity<Object> handleFileConvertFail(FileConvertFailException e) {
public ResponseEntity<Object> handleFileConvertFail(FileConvertFailException e, HttpServletRequest request) {
logInfo(e.getErrorCode(), e, request);
return handleExceptionInternal(e.getErrorCode());
}

Expand All @@ -53,12 +76,14 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgument() {
public ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException e, HttpServletRequest request) {
logInfo(GlobalErrorCode.INVALID_PARAMETER, e, request);
return handleExceptionInternal(GlobalErrorCode.INVALID_PARAMETER);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllException() {
public ResponseEntity<Object> handleAllException(Exception e, HttpServletRequest request) {
logError(e, request);
return handleExceptionInternal(GlobalErrorCode.INTERNAL_SERVER_ERROR);
}

Expand Down Expand Up @@ -121,4 +146,33 @@ private ErrorResponse makeErrorResponse(BindException e) {
.results(new ValidationErrors(validationErrorList))
.build();
}

private void logInfo(ErrorCode ec, Exception e, HttpServletRequest request) {
log.info(LOG_FORMAT_INFO, request.getMethod(), request.getRequestURI(), getUserId(),
getRole(), ec.getHttpStatus(), e.getClass().getName(), e.getMessage());
}

private void logError(Exception e, HttpServletRequest request) {
log.error(LOG_FORMAT_ERROR, request.getMethod(), request.getRequestURI(), getUserId(), getRole(), e);
}

private String getUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName(); // 사용자의 id
} else {
return "anonymous";
}
}

private String getRole() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null && authentication.isAuthenticated()) {
return authentication.getAuthorities().toString(); // 사용자의 role
} else {
return "anonymous";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.shwimping.be.review.application;

import com.amazonaws.util.StringUtils;
import com.shwimping.be.global.application.NCPStorageService;
import com.shwimping.be.place.application.PlaceService;
import com.shwimping.be.place.domain.Place;
import com.shwimping.be.review.dto.request.ReviewUploadRequest;
import com.shwimping.be.review.dto.response.ReviewSimpleResponse;
import com.shwimping.be.review.dto.response.ReviewSimpleResponseList;
import com.shwimping.be.review.exception.CanNotDeleteReviewException;
import com.shwimping.be.review.exception.ReviewNotFoundException;
import com.shwimping.be.review.exception.errorcode.ReviewErrorCode;
import com.shwimping.be.review.repository.ReviewRepository;
import com.shwimping.be.user.application.UserService;
import com.shwimping.be.user.domain.User;
Expand Down Expand Up @@ -50,4 +54,24 @@ public void uploadReview(Long userId, ReviewUploadRequest reviewUploadRequest, M

reviewRepository.save(reviewUploadRequest.toEntity(place, user, imageUrl));
}

@Transactional
public void deleteReview(Long userId, Long reviewId) {
reviewRepository.findById(reviewId)
.ifPresentOrElse(
review -> {
if (review.getUser().getId().equals(userId)) {
if (StringUtils.hasValue(review.getReviewImageUrl())) {
ncpStorageService.deleteFile(review.getReviewImageUrl());
}
reviewRepository.delete(review);
} else {
throw new CanNotDeleteReviewException(ReviewErrorCode.CANNOT_DELETE_REVIEW);
}
},
() -> {
throw new ReviewNotFoundException(ReviewErrorCode.REVIEW_NOT_FOUND);
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shwimping.be.review.exception;

import com.shwimping.be.global.exception.errorcode.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class CanNotDeleteReviewException extends RuntimeException {
private final ErrorCode errorCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shwimping.be.review.exception;

import com.shwimping.be.global.exception.errorcode.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class ReviewNotFoundException extends RuntimeException {
private final ErrorCode errorCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.shwimping.be.review.exception.errorcode;

import com.shwimping.be.global.exception.errorcode.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum ReviewErrorCode implements ErrorCode {

REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "Review not found"),
CANNOT_DELETE_REVIEW(HttpStatus.FORBIDDEN, "Author of the review can only delete the review"),
;

private final HttpStatus httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -62,4 +63,18 @@ public ResponseEntity<ResponseTemplate<?>> uploadReview(
.status(HttpStatus.OK)
.body(ResponseTemplate.EMPTY_RESPONSE);
}

// 리뷰 삭제
@Operation(summary = "리뷰 삭제", description = "리뷰 삭제, 본인의 리뷰만 삭제 가능")
@DeleteMapping("/{reviewId}")
public ResponseEntity<ResponseTemplate<?>> deleteReview(
@AuthenticationPrincipal Long userId,
@PathVariable Long reviewId) {

reviewService.deleteReview(userId, reviewId);

return ResponseEntity
.status(HttpStatus.OK)
.body(ResponseTemplate.EMPTY_RESPONSE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@


import com.shwimping.be.global.util.DummyDataInit;
import com.shwimping.be.global.util.NCPProperties;
import com.shwimping.be.user.domain.User;
import com.shwimping.be.user.domain.type.Provider;
import com.shwimping.be.user.repository.UserRepository;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Order(1)
@DummyDataInit
public class UserInitializer implements ApplicationRunner {

@Value("${cloud.aws.cdn.domain}")
private String defaultProfileImageUrl;

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final NCPProperties ncpProperties;

private static final String DUMMY_PROFILE_IMAGE_URL = "/profile/ic_profile.svg";

Expand All @@ -39,7 +40,7 @@ public void run(ApplicationArguments args) {
.nickname("관리자")
.fcmToken("fcmToken")
.isAlarmAllowed(true)
.profileImageUrl(ncpProperties.s3().endpoint() + ncpProperties.s3().bucket() + DUMMY_PROFILE_IMAGE_URL)
.profileImageUrl(defaultProfileImageUrl + DUMMY_PROFILE_IMAGE_URL)
.email("[email protected]")
.password(passwordEncoder.encode("adminPassword"))
.provider(Provider.SELF)
Expand All @@ -50,7 +51,7 @@ public void run(ApplicationArguments args) {
.nickname("user1")
.fcmToken("fcmToken")
.isAlarmAllowed(true)
.profileImageUrl(ncpProperties.s3().endpoint() + ncpProperties.s3().bucket() + DUMMY_PROFILE_IMAGE_URL)
.profileImageUrl(defaultProfileImageUrl + DUMMY_PROFILE_IMAGE_URL)
.email("[email protected]")
.password(passwordEncoder.encode("user1Password"))
.provider(Provider.SELF)
Expand All @@ -61,7 +62,7 @@ public void run(ApplicationArguments args) {
.nickname("user2")
.fcmToken("fcmToken")
.isAlarmAllowed(true)
.profileImageUrl(ncpProperties.s3().endpoint() + ncpProperties.s3().bucket() + DUMMY_PROFILE_IMAGE_URL)
.profileImageUrl(defaultProfileImageUrl + DUMMY_PROFILE_IMAGE_URL)
.email("[email protected]")
.password(passwordEncoder.encode("user2Password"))
.provider(Provider.SELF)
Expand Down