Skip to content

Commit

Permalink
Merge branch 'develop' into feat/find-registered-product-auction
Browse files Browse the repository at this point in the history
  • Loading branch information
YeaChan05 authored Oct 1, 2024
2 parents c678c32 + e344d1b commit d9808a4
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.chzz.market.common.config.LoginUser;
import org.chzz.market.domain.auction.dto.request.BaseRegisterRequest;
import org.chzz.market.domain.auction.dto.request.StartAuctionRequest;
Expand All @@ -13,8 +14,6 @@
import org.chzz.market.domain.auction.type.AuctionViewType;
import org.chzz.market.domain.bid.service.BidService;
import org.chzz.market.domain.product.entity.Product.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand All @@ -31,12 +30,11 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/auctions")
public class AuctionController {
private static final Logger logger = LoggerFactory.getLogger(AuctionController.class);

private final AuctionService auctionService;
private final BidService bidService;
private final AuctionRegistrationServiceFactory registrationServiceFactory;
Expand Down Expand Up @@ -115,7 +113,7 @@ public ResponseEntity<RegisterResponse> registerAuction(
@PostMapping("/start")
public ResponseEntity<StartAuctionResponse> startAuction(@LoginUser Long userId, @RequestBody @Valid StartAuctionRequest request) {
StartAuctionResponse response = auctionService.startAuction(userId, request);
logger.info("경매 상품으로 성공적으로 전환되었습니다. 상품 ID: {}", response.productId());
log.info("경매 상품으로 성공적으로 전환되었습니다. 상품 ID: {}", response.productId());
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.chzz.market.common.validation.annotation.ThousandMultiple;
import org.chzz.market.domain.auction.type.AuctionRegisterType;

@Getter
Expand All @@ -37,7 +38,7 @@ public abstract class BaseRegisterRequest {
protected Category category;

@NotNull
@Min(value = 1000, message = "시작 가격은 최소 1,000원 이상, 1000의 배수이어야 합니다")
@ThousandMultiple
protected Integer minPrice;

@NotNull(message = "경매 타입을 선택해주세요")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
public enum ImageErrorCode implements ErrorCode {
IMAGE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 업로드를 실패했습니다."),
IMAGE_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 삭제를 실패했습니다. "),
IMAGE_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 저장을 실패했습니다.");
IMAGE_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 저장을 실패했습니다."),
INVALID_IMAGE_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 확장자입니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
package org.chzz.market.domain.image.service;

import static org.chzz.market.domain.image.error.ImageErrorCode.IMAGE_DELETE_FAILED;
import static org.chzz.market.domain.image.error.ImageErrorCode.INVALID_IMAGE_EXTENSION;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.AmazonS3;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.chzz.market.domain.image.entity.Image;
import org.chzz.market.domain.image.error.ImageErrorCode;
import org.chzz.market.domain.image.error.exception.ImageException;
import org.chzz.market.domain.image.repository.ImageRepository;
import org.chzz.market.domain.product.entity.Product;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class ImageService {

private static final Logger logger = LoggerFactory.getLogger(ImageService.class);

private final ImageUploader imageUploader;
private final ImageRepository imageRepository;
private final AmazonS3 amazonS3Client;
Expand All @@ -42,7 +43,7 @@ public List<String> uploadImages(List<MultipartFile> images) {
.map(this::uploadImage)
.toList();

uploadedUrls.forEach(url -> logger.info("업로드 된 이미지 : {}", getFullImageUrl(url)));
uploadedUrls.forEach(url -> log.info("업로드 된 이미지 : {}", cloudfrontDomain + url));

return uploadedUrls;
}
Expand All @@ -51,7 +52,9 @@ public List<String> uploadImages(List<MultipartFile> images) {
* 단일 이미지 파일 업로드 및 CDN 경로 리스트 반환
*/
private String uploadImage(MultipartFile image) {
return imageUploader.uploadImage(image);
String uniqueFileName = createUniqueFileName(Objects.requireNonNull(image.getOriginalFilename()));

return imageUploader.uploadImage(image, uniqueFileName);
}

/**
Expand All @@ -61,7 +64,7 @@ private String uploadImage(MultipartFile image) {
public List<Image> saveProductImageEntities(Product product, List<String> cdnPaths) {
List<Image> images = cdnPaths.stream()
.map(cdnPath -> Image.builder()
.cdnPath(cdnPath)
.cdnPath(cloudfrontDomain + cdnPath)
.product(product)
.build())
.toList();
Expand All @@ -73,8 +76,8 @@ public List<Image> saveProductImageEntities(Product product, List<String> cdnPat
/**
* 업로드된 이미지 삭제
*/
public void deleteUploadImages(List<String> cdnPaths) {
cdnPaths.forEach(this::deleteImage);
public void deleteUploadImages(List<String> fullImageUrls) {
fullImageUrls.forEach(this::deleteImage);
}

/**
Expand All @@ -85,16 +88,30 @@ private void deleteImage(String cdnPath) {
String key = cdnPath.substring(1);
amazonS3Client.deleteObject(bucket, key);
} catch (AmazonServiceException e) {
throw new ImageException(ImageErrorCode.IMAGE_DELETE_FAILED);
throw new ImageException(IMAGE_DELETE_FAILED);
}
}

/**
* CDN 경로로부터 전체 이미지 URL 재구성
* 이미지 -> 서버에 들어왔는지 확인하는 로그에 사용
* 고유한 파일 이름 생성
*/
private String createUniqueFileName(String originalFileName) {
String uuid = UUID.randomUUID().toString();
String extension = StringUtils.getFilenameExtension(originalFileName);

if (extension == null || !isValidFileExtension(extension)) {
throw new ImageException(INVALID_IMAGE_EXTENSION);
}

return uuid;
}

/**
* 파일 확장자 검증
*/
public String getFullImageUrl(String cdnPath) {
return "https://" + cloudfrontDomain + cdnPath;
private boolean isValidFileExtension(String extension) {
List<String> allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "webp");
return allowedExtensions.contains(extension.toLowerCase());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
테스트 이미지 업로드 인터페이스
*/
public interface ImageUploader {
String uploadImage(MultipartFile image);
String uploadImage(MultipartFile image, String fileName);
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.chzz.market.domain.image.error.ImageErrorCode;
import org.chzz.market.domain.image.error.exception.ImageException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@Service
@RequiredArgsConstructor
public class S3ImageUploader implements ImageUploader {
Expand All @@ -20,16 +19,15 @@ public class S3ImageUploader implements ImageUploader {
private String bucket;

@Override
public String uploadImage(MultipartFile image) {
String fileName = image.getOriginalFilename();
public String uploadImage(MultipartFile image, String fileName) {
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(image.getSize());
metadata.setContentType(image.getContentType());

amazonS3Client.putObject(bucket, fileName, image.getInputStream(), metadata);

return "/" + fileName; // CDN 경로 생성 (전체 URL 아닌 경로만)
return fileName; // CDN 경로 생성 (전체 URL 아닌 경로만)
} catch (IOException e) {
throw new ImageException(ImageErrorCode.IMAGE_UPLOAD_FAILED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.chzz.market.common.config.LoginUser;
import org.chzz.market.domain.like.dto.LikeResponse;
import org.chzz.market.domain.like.service.LikeService;
Expand All @@ -15,8 +16,6 @@
import org.chzz.market.domain.product.dto.UpdateProductRequest;
import org.chzz.market.domain.product.dto.UpdateProductResponse;
import org.chzz.market.domain.product.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand All @@ -33,12 +32,12 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/products")
public class ProductController {
private static final Logger logger = LoggerFactory.getLogger(ProductController.class);

private final ProductService productService;
private final LikeService likeService;

Expand Down Expand Up @@ -120,7 +119,7 @@ public ResponseEntity<DeleteProductResponse> deleteProduct(
@PathVariable Long productId,
@LoginUser Long userId) {
DeleteProductResponse response = productService.deleteProduct(productId, userId);
logger.info("상품이 성공적으로 삭제되었습니다. 상품 ID: {}", productId);
log.info("상품이 성공적으로 삭제되었습니다. 상품 ID: {}", productId);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public class Product extends BaseTimeEntity {
private String description;

@Column
@ThousandMultiple
private Integer minPrice;

@Column(nullable = false, columnDefinition = "varchar(30)")
Expand Down

0 comments on commit d9808a4

Please sign in to comment.