일다 AWS에서 S3 버킷을 생성한다.
순서대로 만든다.
IAM 사용자 권한 추가
S3에 접근하기 위해서는 IAM 사용자에게 S3 접근 권한을 주고, 그 사용자의 액세스 키, 비밀 엑세스 키를 사용해야 한다.
사용자를 만들고
권한을 설정한다.
설정후에 사용자 추가를 하면 액세스 키, 비밀 엑세스 키가 보여지는데 이 키들은 현재 화면에서 밖에 볼 수 없다. 저장해두자.
Spring Boot로 파일 업로드
위에 s3 버킷 설정을 다해주면 이제 스프링부트 프로젝트만 수정해주면 된다.
build.gradle에 의존성 추가
//S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.yml 작성하기
cloud:
aws:
s3:
bucket: ${BUCKET}
stack:
auto: false
region:
static: ap-northeast-2
credentials:
access-key: ${ACCESS_KEY}
secret-key: ${SECRET_KEY}
S3config 작성하기
@Configuration
public class S3Config {
// AWS 자격 증명(access key와 secret key)을 외부 프로퍼티에서 가져오기 위한 필드
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
// AWS S3 클라이언트를 생성할 때 사용할 리전 정보를 외부 프로퍼티에서 가져오기 위한 필드
@Value("${cloud.aws.region.static}")
private String region;
// AmazonS3Client 빈을 생성하는 메서드
@Bean
public AmazonS3Client amazonS3Client() {
// AWS 자격 증명 객체 생성
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
// AmazonS3 클라이언트를 생성하고 반환
return (AmazonS3Client) AmazonS3ClientBuilder
.standard()
.withRegion(region) // 생성할 클라이언트의 리전 설정
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) // 자격 증명 제공자 설정
.build();
}
}
이 코드는 AWS S3 클라이언트를 구성하는 Spring Bean을 생성하는데 사용되는 Java Configuration 클래스인 것 같습니다. 주로 AWS S3를 사용하기 위한 AWS 자격 증명(access key와 secret key) 및 리전(region) 정보를 포함하고 있습니다.
imageUpload 작성
public String imageUpload(@RequestParam("upload") MultipartFile file) throws IOException {
// 1. 업로드된 파일의 원본 파일명을 가져옵니다.
String fileName = file.getOriginalFilename();
// 2. 파일의 확장자를 추출합니다.
String ext = fileName.substring(fileName.lastIndexOf("."));
// 3. 고유한 식별자(UUID)를 생성하여 파일명에 확장자를 추가하여 새로운 파일명을 만듭니다.
String uuidFileName = UUID.randomUUID() + ext;
try (InputStream inputStream = file.getInputStream()) {
// 4. MultipartFile에서 InputStream을 얻어서 AWS S3에 업로드할 준비를 합니다.
// 5. AWS S3 객체에 대한 메타데이터를 설정하는 객체를 생성합니다.
ObjectMetadata metadata = new ObjectMetadata();
// 6. InputStream에서 바이트 배열로 데이터를 읽어옵니다.
byte[] bytes = IOUtils.toByteArray(inputStream);
// 7. 메타데이터에 업로드된 파일의 컨텐츠 타입을 설정합니다.
metadata.setContentType(file.getContentType());
// 8. 메타데이터에 업로드된 파일의 크기를 설정합니다.
metadata.setContentLength(bytes.length);
// 9. 바이트 배열을 이용하여 ByteArrayInputStream을 생성합니다.
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 10. AWS S3 클라이언트를 사용하여 이미지를 업로드합니다.
s3Config.amazonS3Client().putObject(
new PutObjectRequest(bucket, uuidFileName, byteArrayInputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
}
// 11. 업로드된 이미지의 S3 URL을 생성합니다.
String s3Url = s3Config.amazonS3Client().getUrl(bucket, uuidFileName).toString();
// 12. 생성된 S3 URL을 반환합니다.
return s3Url;
}
이 코드는 Spring에서 AWS S3를 사용하여 이미지를 업로드하고, 해당 이미지의 고유한 URL을 생성하여 반환하는 간단한 메서드입니다. 업로드된 이미지는 S3 버킷에서 공개 읽기 권한이 부여된 상태로 저장됩니다.
모델 작성하기
@Entity
@NoArgsConstructor
@Getter
@Table(name="TB_IMAGE")
public class ImageFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String imagePathUrl;
// 다대일(N:1) 관계에서 여러 개의 ImageFile이 하나의 User에 매핑됩니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// 다대일(N:1) 관계에서 여러 개의 ImageFile이 하나의 Product에 매핑됩니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
// Lombok의 @Builder 어노테이션으로 생성된 빌더 패턴을 사용하여 객체를 생성할 수 있습니다.
@Builder
private ImageFile(String imagePathUrl, User user, Product product){
this.imagePathUrl = imagePathUrl;
this.user = user;
this.product = product;
};
}
요약하면, 이 코드는 이미지 파일 정보를 저장하는 JPA 엔터티로, 해당 이미지 파일이 어떤 사용자(User) 또는 어떤 상품(Product)에 속하는지를 나타내고 있습니다.
래퍼지토리 작성
package com.example.booktalk.domain.imageFile.repository;
import com.example.booktalk.domain.imageFile.entity.ImageFile;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ImageFileRepository extends JpaRepository<ImageFile, Long> {
List<ImageFile> findByProductId(Long productId);
}
이 레포지토리 인터페이스는 Spring Data JPA에서 제공하는 JpaRepository를 확장하여 ImageFile 엔터티에 대한 기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행할 수 있도록 합니다. 또한 findByProductId 메서드를 통해 특정 상품(Product)에 속하는 이미지 파일 목록을 조회할 수 있습니다.
package com.example.booktalk.domain.product.controller;
import com.example.booktalk.domain.product.dto.request.ProductCreateReq;
import com.example.booktalk.domain.product.dto.request.ProductUpdateReq;
import com.example.booktalk.domain.product.dto.response.ProductCreateRes;
import com.example.booktalk.domain.product.dto.response.ProductDeleteRes;
import com.example.booktalk.domain.product.dto.response.ProductGetRes;
import com.example.booktalk.domain.product.dto.response.ProductListRes;
import com.example.booktalk.domain.product.dto.response.ProductSerachListRes;
import com.example.booktalk.domain.product.dto.response.ProductTagListRes;
import com.example.booktalk.domain.product.dto.response.ProductTopLikesListRes;
import com.example.booktalk.domain.product.dto.response.ProductUpdateRes;
import com.example.booktalk.domain.product.service.ProductService;
import com.example.booktalk.global.security.UserDetailsImpl;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
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;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/products")
public class ProductController {
private final ProductService productService;
@PostMapping
public ResponseEntity<ProductCreateRes> createProduct(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@RequestPart("req") ProductCreateReq req,
@RequestParam("upload") List<MultipartFile> files
) throws IOException {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.createProduct(userDetails.getUser().getId(), req, files));
}
@PutMapping("/{productId}")
public ResponseEntity<ProductUpdateRes> updateProduct(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable Long productId,
@RequestPart("req") ProductUpdateReq req,
@RequestParam("upload") List<MultipartFile> files
) throws IOException {
return ResponseEntity.status(HttpStatus.OK)
.body(
productService.updateProduct(userDetails.getUser().getId(), productId, req, files));
}
@DeleteMapping("/{productId}")
public ResponseEntity<ProductDeleteRes> deleteProduct(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable Long productId
) {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.deleteProduct(userDetails.getUser().getId(), productId));
}
@GetMapping("/{productId}") //단일 조회
public ResponseEntity<ProductGetRes> getProduct(
@PathVariable Long productId
) {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.getProduct(productId));
}
@GetMapping //상품 리스트 조회
public ResponseEntity<Page<ProductListRes>> getProductList(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy,
@RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc
) {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.getProductList(page - 1, size, sortBy, isAsc));
}
@GetMapping("/main") //메인화면에 관심상품 Top3 인 product 출력
public ResponseEntity<List<ProductTopLikesListRes>> getProductListTopThree() {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.getProductListByLikesTopThree());
}
@GetMapping("/search") //상품 검색 리스트 조회
public ResponseEntity<Page<ProductSerachListRes>> getProductSearchList(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy,
@RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc,
@RequestParam(value = "query") String search
) {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.getProductSearchList(page - 1, size, sortBy, isAsc, search));
}
@GetMapping("/tag") //상품 검색 리스트 조회
public ResponseEntity<Page<ProductTagListRes>> getProductListByTag(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam(value = "sortBy", defaultValue = "createdAt") String sortBy,
@RequestParam(value = "isAsc", defaultValue = "false") boolean isAsc,
@RequestParam(value = "tag") String search
) {
return ResponseEntity.status(HttpStatus.OK)
.body(productService.getProductSearchTagList(page - 1, size, sortBy, isAsc, search));
}
}
List<MultipartFile> files로 이미지를 받아준다.
서비스 작성
@Service
@RequiredArgsConstructor
@Transactional
public class ImageFileService {
private final UserRepository userRepository;
private final S3Config s3Config;
private final ImageFileRepository imageFileRepository;
private final ProductRepository productRepository;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
/**
* 이미지 업로드 및 저장 후 생성된 경로를 반환합니다.
* @param file 업로드된 파일
* @return 이미지의 S3 경로
* @throws IOException 입출력 예외
*/
public String imageUpload(@RequestParam("upload") MultipartFile file) throws IOException {
// 파일명과 확장자 분리하여 UUID를 추가한 새로운 파일명 생성
String fileName = file.getOriginalFilename();
String ext = fileName.substring(fileName.lastIndexOf("."));
String uuidFileName = UUID.randomUUID() + ext;
// 파일을 S3에 업로드
try (InputStream inputStream = file.getInputStream()) {
ObjectMetadata metadata = new ObjectMetadata();
byte[] bytes = IOUtils.toByteArray(inputStream);
metadata.setContentType(file.getContentType());
metadata.setContentLength(bytes.length);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
s3Config.amazonS3Client().putObject(
new PutObjectRequest(bucket, uuidFileName, byteArrayInputStream,
metadata).withCannedAcl(
CannedAccessControlList.PublicRead));
}
// S3에 업로드된 파일의 경로 반환
String s3Url = s3Config.amazonS3Client().getUrl(bucket, uuidFileName).toString();
return s3Url;
}
/**
* 사용자 및 이미지 파일 권한 검증을 수행합니다.
* @param user 사용자 객체
* @param imageFile 이미지 파일 객체
*/
private void validateProductUser(User user, ImageFile imageFile) {
if (!user.getId().equals(imageFile.getUser().getId())
&& !user.getRole().equals(UserRoleType.ADMIN)) {
throw new NotPermissionAuthority(ProductErrorCode.NOT_PERMISSION_AUTHORITHY);
}
}
/**
* 이미지를 생성하고 해당 이미지 파일 정보를 반환합니다.
* @param userId 사용자 식별자
* @param productId 상품 식별자
* @param files 업로드된 이미지 파일 리스트
* @return 생성된 이미지 파일 정보 리스트
* @throws IOException 입출력 예외
*/
public List<ImageCreateRes> createImage(Long userId, Long productId, List<MultipartFile> files)
throws IOException {
List<ImageCreateRes> imageCreateResList = new ArrayList<>();
// 각 파일에 대해 이미지 업로드 및 정보 저장
for (MultipartFile file : files) {
String imagePathUrl = imageUpload(file);
User user = userRepository.findUserByIdWithThrow(userId);
Product product = productRepository.findProductByIdWithThrow(productId);
ImageFile imageFile = ImageFile.builder()
.imagePathUrl(imagePathUrl)
.user(user)
.product(product)
.build();
imageFileRepository.save(imageFile);
ImageCreateRes imageResponse = new ImageCreateRes(imageFile.getImagePathUrl());
imageCreateResList.add(imageResponse);
}
return imageCreateResList;
}
/**
* 상품 식별자에 해당하는 이미지 파일 정보 리스트를 조회합니다.
* @param productId 상품 식별자
* @return 이미지 파일 정보 리스트
*/
@Transactional(readOnly = true)
public List<ImageListRes> getImages(Long productId) {
List<ImageFile> imageList = imageFileRepository.findByProductId(productId);
return imageList.stream()
.map(imageFile -> new ImageListRes(imageFile.getImagePathUrl()))
.toList();
}
/**
* 이미지를 업데이트하고 해당 이미지 파일 정보를 반환합니다.
* @param userId 사용자 식별자
* @param productId 상품 식별자
* @param files 업로드된 이미지 파일 리스트
* @return 업데이트된 이미지 파일 정보 리스트
* @throws IOException 입출력 예외
*/
public List<ImageCreateRes> updateImage(Long userId, Long productId, List<MultipartFile> files)
throws IOException {
// 이미지 삭제 후 새로운 이미지 생성
deleteImage(userId, productId);
return createImage(userId, productId, files);
}
/**
* 이미지를 삭제하고 해당 상품의 이미지 삭제 응답을 반환합니다.
* @param userId 사용자 식별자
* @param productId 상품 식별자
* @return 이미지 삭제 응답
*/
public ImageDeleteRes deleteImage(Long userId, Long productId) {
User user = userRepository.findUserByIdWithThrow(userId);
List<ImageFile> imageFileList = imageFileRepository.findByProductId(productId);
for (ImageFile imageFile : imageFileList) {
// 이미지 파일 권한 검증 및 삭제
validateProductUser(user, imageFile);
imageFileRepository.delete(imageFile);
}
return new ImageDeleteRes("삭제가 완료되었습니다.");
}
}
imageFileService을 작성한후 ProductService에서 가져가서 쓴다.
package com.example.booktalk.domain.product.service;
import com.example.booktalk.domain.category.entity.Category;
import com.example.booktalk.domain.category.exception.CategoryErrorCode;
import com.example.booktalk.domain.category.exception.NotFoundCategoryException;
import com.example.booktalk.domain.category.repository.CategoryRepository;
import com.example.booktalk.domain.imageFile.dto.response.ImageCreateRes;
import com.example.booktalk.domain.imageFile.dto.response.ImageListRes;
import com.example.booktalk.domain.imageFile.service.ImageFileService;
import com.example.booktalk.domain.product.dto.request.ProductCreateReq;
import com.example.booktalk.domain.product.dto.request.ProductUpdateReq;
import com.example.booktalk.domain.product.dto.response.ProductCreateRes;
import com.example.booktalk.domain.product.dto.response.ProductDeleteRes;
import com.example.booktalk.domain.product.dto.response.ProductGetRes;
import com.example.booktalk.domain.product.dto.response.ProductListRes;
import com.example.booktalk.domain.product.dto.response.ProductSerachListRes;
import com.example.booktalk.domain.product.dto.response.ProductTagListRes;
import com.example.booktalk.domain.product.dto.response.ProductTopLikesListRes;
import com.example.booktalk.domain.product.dto.response.ProductUpdateRes;
import com.example.booktalk.domain.product.entity.Product;
import com.example.booktalk.domain.product.exception.NotPermissionAuthority;
import com.example.booktalk.domain.product.exception.ProductErrorCode;
import com.example.booktalk.domain.product.repository.ProductRepository;
import com.example.booktalk.domain.productcategory.entity.ProductCategory;
import com.example.booktalk.domain.productcategory.repository.ProductCategoryRepository;
import com.example.booktalk.domain.user.dto.response.UserRes;
import com.example.booktalk.domain.user.entity.User;
import com.example.booktalk.domain.user.entity.UserRoleType;
import com.example.booktalk.domain.user.repository.UserRepository;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@Service
@RequiredArgsConstructor
@Transactional
public class ProductService {
private final ProductRepository productRepository;
private final UserRepository userRepository;
private final CategoryRepository categoryRepository;
private final ProductCategoryRepository productCategoryRepository;
private final ImageFileService imageFileService;
public ProductCreateRes createProduct(Long userId, ProductCreateReq req,
List<MultipartFile> files) throws IOException {
User user = userRepository.findUserByIdWithThrow(userId);
Product product = Product.builder()
.name(req.name())
.price(req.price())
.quantity(req.quantity())
.region(req.region())
.content(req.content())
.user(user)
.build();
product = productRepository.save(product);
addCategory(req.categoryList(), product);
UserRes userRes = new UserRes(user.getId(), user.getNickname());
List<ImageCreateRes> imageCreateResList = imageFileService.createImage(userId,
product.getId(), files);
return new ProductCreateRes(product.getId(), product.getName(), product.getQuantity(),
product.getPrice()
, product.getRegion(), product.getFinished(), userRes, product.getContent(),
req.categoryList(), imageCreateResList);
//TODO 생성자로 한줄정리
}
public ProductUpdateRes updateProduct(Long userId, Long productId, ProductUpdateReq req,
List<MultipartFile> files) throws IOException {
User user = userRepository.findUserByIdWithThrow(userId);
Product product = productRepository.findProductByIdWithThrow(productId);
validateProductUser(user, product);
List<ImageCreateRes> imageCreateResList = imageFileService.updateImage(userId, productId,
files);
product.update(req);
updateCategory(req.categoryList(), product);
UserRes userRes = new UserRes(user.getId(), user.getNickname());
return new ProductUpdateRes(product.getId(), product.getName(),
product.getQuantity(), product.getPrice(), product.getRegion(),
product.getFinished(), userRes, product.getProductLikeCnt(), product.getContent(),
req.categoryList(), imageCreateResList);
}
@Transactional(readOnly = true)
public ProductGetRes getProduct(Long productId) {
Product product = productRepository.findProductByIdWithThrow(productId);
User user = product.getUser();
UserRes userRes = new UserRes(user.getId(), user.getNickname());
List<String> categories = product.getProductCategoryList().stream()
.map(productCategory -> {
return productCategory.getCategory().getName();
})
.toList();
List<ImageListRes> imageListRes = imageFileService.getImages(productId);
return new ProductGetRes(product.getId(), product.getName(), product.getPrice()
, product.getQuantity(), userRes, product.getRegion(), categories,
product.getProductLikeCnt(), product.getContent(),
product.getFinished(), imageListRes);
}
@Transactional(readOnly = true)
public Page<ProductListRes> getProductList(int page, int size, String sortBy, boolean isAsc) {
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
Page<Product> productList = productRepository.findAllByDeletedFalse(pageable);
return productList
.map(product -> {
List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());
List<String> categories = product.getProductCategoryList().stream()
.map(productCategory -> {
return productCategory.getCategory().getName();
})
.toList();
ImageListRes imageGetRes = imageListRes.isEmpty() ? null : imageListRes.get(0);
return new ProductListRes(product.getId(), product.getName(), product.getPrice(),
product.getQuantity(), product.getProductLikeCnt(), categories,
product.getRegion(), imageGetRes);
});
}
@Transactional(readOnly = true)
public List<ProductTopLikesListRes> getProductListByLikesTopThree() {
List<Product> productList = productRepository.findTop3ByDeletedFalseOrderByProductLikeCntDesc();
return productList.stream()
.map(product -> {
List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());
List<String> categories = product.getProductCategoryList().stream()
.map(productCategory -> {
return productCategory.getCategory().getName();
})
.toList();
ImageListRes imageGetRes =
imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);
return new ProductTopLikesListRes(product.getId(), product.getName(),
product.getPrice(),
product.getQuantity(), product.getProductLikeCnt(), categories,
product.getRegion(), imageGetRes);
})
.toList();
}
@Transactional(readOnly = true)
public Page<ProductSerachListRes> getProductSearchList(int page, int size, String sortBy,
Boolean isAsc,
String search) {
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
Page<Product> productList = productRepository.getPostListByName(pageable, search);
return productList
.map(product -> {
List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());
List<String> categories = product.getProductCategoryList().stream()
.map(productCategory -> {
return productCategory.getCategory().getName();
})
.toList();
ImageListRes imageGetRes =
imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);
return new ProductSerachListRes(product.getId(), product.getName(),
product.getPrice(),
product.getQuantity(), product.getProductLikeCnt(), categories,
product.getRegion(), imageGetRes);
});
}
@Transactional(readOnly = true)
public Page<ProductTagListRes> getProductSearchTagList(int page, int size, String sortBy,
Boolean isAsc,
String tag) {
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
Page<Product> productList = productRepository.getProductListByTag(pageable, tag);
return productList
.map(product -> {
List<ImageListRes> imageListRes = imageFileService.getImages(product.getId());
List<String> categories = product.getProductCategoryList().stream()
.map(productCategory -> {
return productCategory.getCategory().getName();
})
.toList();
ImageListRes imageGetRes =
imageListRes.isEmpty() ? new ImageListRes(null) : imageListRes.get(0);
return new ProductTagListRes(product.getId(), product.getName(), product.getPrice(),
product.getQuantity(), product.getProductLikeCnt(), categories,
product.getRegion(), imageGetRes);
});
}
public ProductDeleteRes deleteProduct(Long userId, Long productId) {
User user = userRepository.findUserByIdWithThrow(userId);
Product product = productRepository.findProductByIdWithThrow(productId);
validateProductUser(user, product);
imageFileService.deleteImage(userId, productId);
product.deleted();
return new ProductDeleteRes("삭제가 완료되었습니다.");
}
private void validateProductUser(User user, Product product) {
if (!user.getId().equals(product.getUser().getId())
&& !user.getRole().equals(UserRoleType.ADMIN)) {
throw new NotPermissionAuthority(ProductErrorCode.NOT_PERMISSION_AUTHORITHY);
}
}
private List<Category> findCategoryList(List<String> categoryList) {
List<Category> categories = categoryRepository.findByNameIn(categoryList);
if (categoryList.size() != categories.size()) {
throw new NotFoundCategoryException(CategoryErrorCode.NOT_FOUND_CATEGORY);
}
return categories;
}
private void addCategory(List<String> categoryList, Product product) {
List<Category> categories = findCategoryList(categoryList);
categories.forEach(category -> {
ProductCategory productCategory = ProductCategory.builder()
.category(category)
.product(product)
.build();
productCategoryRepository.save(productCategory);
product.addProductCategory(productCategory);
});
}
private void removeCategory(List<String> categoryList, Product product) {
List<ProductCategory> productCategoryList = productCategoryRepository.findAllByProductAndCategory_NameIn(
product, categoryList);
productCategoryList.forEach(productCategory ->
{
productCategoryRepository.delete(productCategory);
//product.removeProductCategory(productCategory); remove도 따로 만들어줘야할까..?
});
}
private void updateCategory(List<String> reqCategoryList, Product product) {
List<String> currentCategories = product.getProductCategoryList()
.stream() //성능이슈 확인 N + 1
.map(ProductCategory::getCategory)
.map(Category::getName).toList();
List<String> removeableCategoryList = currentCategories.stream()
.filter(category -> !reqCategoryList.contains(category))
.toList();
removeCategory(removeableCategoryList, product);
List<String> addableCategoryList = reqCategoryList.stream()
.filter(category -> !currentCategories.contains(category))
.toList();
addCategory(addableCategoryList, product);
}
}
'프로젝트 > booktalk(책 중고 거래 서비스)' 카테고리의 다른 글
product (1) | 2024.01.25 |
---|---|
image파일 업로드 할때 리사이징 적용하기 (0) | 2024.01.24 |
1/15 (0) | 2024.01.23 |
1/11 (0) | 2024.01.23 |
1/10 (0) | 2024.01.23 |