Skip to content

Commit

Permalink
Merge pull request #447 from woowacourse-teams/refactor/442-imporve-i…
Browse files Browse the repository at this point in the history
…mage-resize-performence

이미지 리사이즈 성능개선 및 이미지 서비스 리팩토링
  • Loading branch information
green-kong authored Sep 14, 2023
2 parents 8f4c46a + d0292bd commit 5016eef
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.project.yozmcafe.controller.dto.cafe.CafeUpdateRequest;
import com.project.yozmcafe.controller.dto.cafe.MenuBoardRequest;
import com.project.yozmcafe.controller.dto.cafe.MenuRequest;
import com.project.yozmcafe.domain.resizedimage.Size;
import com.project.yozmcafe.service.CafeAdminService;
import com.project.yozmcafe.service.ImageService;
import com.project.yozmcafe.service.MenuService;
Expand All @@ -45,7 +44,7 @@ public CafeAdminController(final CafeAdminService cafeAdminService, final ImageS
@PostMapping
public ResponseEntity<String> save(@RequestPart final CafeRequest request,
@RequestPart final List<MultipartFile> images) {
final List<String> uploadedFileNames = imageService.resizeAndUpload(images, List.of(Size.values()));
final List<String> uploadedFileNames = imageService.resizeToAllSizesAndUpload(images);
final Long savedId = cafeAdminService.save(request, uploadedFileNames);

return ResponseEntity.created(URI.create("/admin/cafes/" + savedId)).build();
Expand All @@ -58,7 +57,7 @@ public ResponseEntity<Void> update(@PathVariable("cafeId") final Long cafeId,
final List<String> originalImages = cafeAdminService.findImagesByCafeId(cafeId);
imageService.deleteAll(originalImages);

final List<String> savedImages = imageService.resizeAndUpload(images, List.of(Size.values()));
final List<String> savedImages = imageService.resizeToAllSizesAndUpload(images);
cafeAdminService.update(cafeId, request, savedImages);

return ResponseEntity.noContent().build();
Expand Down Expand Up @@ -93,7 +92,7 @@ public ResponseEntity<String> saveMenus(@PathVariable("cafeId") final Long cafeI
menuService.saveMenuWithoutImage(cafeId, menuRequest);
}
if (nonNull(image)) {
String uploadedFileName = imageService.resizeAndUpload(image, Size.THUMBNAIL);
final String uploadedFileName = imageService.resizeToThumbnailSizeAndUpload(image);
menuService.saveMenu(cafeId, menuRequest, uploadedFileName);
}

Expand All @@ -104,7 +103,7 @@ public ResponseEntity<String> saveMenus(@PathVariable("cafeId") final Long cafeI
public ResponseEntity<String> saveMenuBoards(@PathVariable("cafeId") final Long cafeId,
@RequestPart final MenuBoardRequest menuBoardRequest,
@RequestPart final MultipartFile image) {
String uploadedFileName = imageService.resizeAndUpload(image, Size.MOBILE);
final String uploadedFileName = imageService.resizeToMobileSizeAndUpload(image);
menuService.saveMenuBoard(cafeId, menuBoardRequest, uploadedFileName);

return ResponseEntity.created(URI.create("/admin/cafes/" + cafeId)).build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package com.project.yozmcafe.domain.resizedimage;

import com.project.yozmcafe.exception.BadRequestException;
import org.springframework.web.multipart.MultipartFile;
import static com.project.yozmcafe.exception.ErrorCode.INVALID_IMAGE_SIZE;
import static com.project.yozmcafe.exception.ErrorCode.NOT_IMAGE;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;

import javax.imageio.ImageIO;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static com.project.yozmcafe.exception.ErrorCode.INVALID_IMAGE_SIZE;
import static com.project.yozmcafe.exception.ErrorCode.NOT_IMAGE;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;
import javax.imageio.ImageIO;

import org.springframework.web.multipart.MultipartFile;

import com.project.yozmcafe.exception.BadRequestException;

public class ImageResizer {

Expand Down Expand Up @@ -46,13 +49,13 @@ private boolean isNotImage(final MultipartFile image) {
return !requireNonNull(contentType).startsWith(IMAGE_FORMAT_PREFIX);
}

public List<MultipartFile> getResizedImages(final List<Size> sizes) {
return sizes.stream()
.map(this::getResizedImage)
public List<MultipartFile> resizeImageToAllSizes() {
return Arrays.stream(Size.values())
.map(this::resizeToFixedSize)
.toList();
}

public MultipartFile getResizedImage(final Size size) {
public MultipartFile resizeToFixedSize(final Size size) {
final BufferedImage bufferedImage = getBufferedImage();

final int width = size.getWidth();
Expand Down
46 changes: 30 additions & 16 deletions server/src/main/java/com/project/yozmcafe/service/ImageService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.project.yozmcafe.service;

import static com.project.yozmcafe.domain.resizedimage.Size.MOBILE;
import static com.project.yozmcafe.domain.resizedimage.Size.THUMBNAIL;

import java.util.List;

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

import com.project.yozmcafe.domain.S3Client;
import com.project.yozmcafe.domain.resizedimage.ImageName;
import com.project.yozmcafe.domain.resizedimage.ImageResizer;
import com.project.yozmcafe.domain.resizedimage.Size;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Service
public class ImageService {
Expand All @@ -18,14 +22,16 @@ public ImageService(final S3Client s3Client) {
this.s3Client = s3Client;
}

public List<String> resizeAndUpload(final List<MultipartFile> files, final List<Size> sizes) {
final List<ImageResizer> resizers = files.stream()
public List<String> resizeToAllSizesAndUpload(final List<MultipartFile> files) {
final List<ImageResizer> imageResizers = files.stream()
.map(this::multipartfileToImageResizer)
.toList();

resizers.forEach(resizer -> resizer.getResizedImages(sizes).forEach(s3Client::upload));
imageResizers.parallelStream()
.forEach(imageResizer -> imageResizer.resizeImageToAllSizes()
.forEach(s3Client::upload));

return resizers.stream()
return imageResizers.stream()
.map(ImageResizer::getFileName)
.toList();
}
Expand All @@ -35,19 +41,27 @@ private ImageResizer multipartfileToImageResizer(final MultipartFile file) {
return new ImageResizer(file, imageName.get());
}

public void deleteAll(final List<String> imageNames) {
imageNames.stream()
.map(Size::getFileNamesWithPath)
.flatMap(List::stream)
.forEach(s3Client::delete);
public String resizeToThumbnailSizeAndUpload(MultipartFile file) {
return resizeToFixedSizeAndUpload(file, THUMBNAIL);
}

public String resizeAndUpload(final MultipartFile image, final Size size) {
final ImageResizer imageResizer = multipartfileToImageResizer(image);
public String resizeToMobileSizeAndUpload(MultipartFile file) {
return resizeToFixedSizeAndUpload(file, MOBILE);
}

private String resizeToFixedSizeAndUpload(final MultipartFile file, final Size size) {
final ImageResizer imageResizer = multipartfileToImageResizer(file);

final MultipartFile resizedImages = imageResizer.getResizedImage(size);
final MultipartFile resizedImages = imageResizer.resizeToFixedSize(size);
s3Client.upload(resizedImages);

return imageResizer.getFileName();
}

public void deleteAll(final List<String> imageNames) {
imageNames.stream()
.map(Size::getFileNamesWithPath)
.flatMap(List::stream)
.forEach(s3Client::delete);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void saveMenu(final Long cafeId,
final Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new BadRequestException(NOT_EXISTED_CAFE));

Menu menu = menuRequest.toMenu(cafe, imageName);
final Menu menu = menuRequest.toMenu(cafe, imageName);
menuRepository.save(menu);
}

Expand All @@ -58,7 +58,7 @@ public void saveMenuWithoutImage(final Long cafeId,
final Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new BadRequestException(NOT_EXISTED_CAFE));

Menu menu = menuRequest.toMenuWithoutImage(cafe);
final Menu menu = menuRequest.toMenuWithoutImage(cafe);
menuRepository.save(menu);
}

Expand All @@ -69,7 +69,7 @@ public void saveMenuBoard(final Long cafeId,
final Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new BadRequestException(NOT_EXISTED_CAFE));

MenuBoard menuBoard = menuBoardRequest.toMenu(cafe, imageName);
final MenuBoard menuBoard = menuBoardRequest.toMenu(cafe, imageName);
menuBoardRepository.save(menuBoard);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package com.project.yozmcafe.domain;

import com.project.yozmcafe.domain.resizedimage.ImageResizer;
import com.project.yozmcafe.domain.resizedimage.Size;
import com.project.yozmcafe.exception.BadRequestException;
import com.project.yozmcafe.exception.ErrorCode;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import com.project.yozmcafe.domain.resizedimage.ImageResizer;
import com.project.yozmcafe.domain.resizedimage.Size;
import com.project.yozmcafe.exception.BadRequestException;
import com.project.yozmcafe.exception.ErrorCode;

class ImageResizerTest {

Expand Down Expand Up @@ -75,14 +78,14 @@ void construct() throws Exception {
}

@Test
@DisplayName("리사이즈된 이미지들을 리턴한다")
void getResizedImages() throws Exception {
@DisplayName("모든 사이즈로 리사이즈된 이미지들을 리턴한다")
void resizeImageToAllSize() throws Exception {
//given
final MultipartFile image = makeMultipartFile();
final ImageResizer imageResizer = new ImageResizer(image, "fileName.png");

//when
final List<MultipartFile> results = imageResizer.getResizedImages(List.of(Size.values()));
final List<MultipartFile> results = imageResizer.resizeImageToAllSizes();
final List<String> fileNameWithPathResult = results.stream()
.map(MultipartFile::getOriginalFilename)
.toList();
Expand All @@ -94,6 +97,22 @@ void getResizedImages() throws Exception {
);
}

@ParameterizedTest
@EnumSource(value = Size.class)
@DisplayName("특정 사이즈로 리사이즈된 이미지들을 리턴한다")
void resizeImageToMobileSize(final Size size) throws Exception {
//given
final String fileName = "fileName.png";
final MultipartFile image = makeMultipartFile();
final ImageResizer imageResizer = new ImageResizer(image, fileName);

//when
final MultipartFile resizedImage = imageResizer.resizeToFixedSize(size);

//then
assertThat(resizedImage.getOriginalFilename()).isEqualTo(size.getFileNameWithPath(fileName));
}

private MultipartFile makeMultipartFile() throws IOException {
final File file = new File("src/test/resources/image.png");
final FileInputStream fileInputStream = new FileInputStream(file);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package com.project.yozmcafe.service;


import com.project.yozmcafe.BaseTest;
import com.project.yozmcafe.domain.S3Client;
import com.project.yozmcafe.domain.resizedimage.Size;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.anyString;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.anyString;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;
import com.project.yozmcafe.BaseTest;
import com.project.yozmcafe.domain.S3Client;
import com.project.yozmcafe.domain.resizedimage.Size;

class ImageServiceTest extends BaseTest {

Expand All @@ -32,14 +33,14 @@ class ImageServiceTest extends BaseTest {
private S3Client s3Client;

@Test
@DisplayName("리사이즈 이후 업로드 한다")
@DisplayName("모든 사이즈로 리사이즈 이후 업로드 한다")
void resizeAndUpload1() {
//given
final MockMultipartFile image = makeMultipartFile();
final List<MultipartFile> images = List.of(image, image, image);

//when
final List<String> names = imageService.resizeAndUpload(images, List.of(Size.values()));
final List<String> names = imageService.resizeToAllSizesAndUpload(images);

//then
final int expectedUploadCount = images.size() * Size.values().length;
Expand All @@ -50,17 +51,16 @@ void resizeAndUpload1() {
}

@Test
@DisplayName("리사이즈 이후 업로드한다")
@DisplayName("모바일 사이즈로 리사이즈 이후 업로드한다")
void resizeAndUpload2() {
//given
final MockMultipartFile file = makeMultipartFile();

//when
imageService.resizeAndUpload(file, Size.MOBILE);
imageService.resizeToMobileSizeAndUpload(file);

//then
verify(s3Client, times(1)).upload(any());

}

@Test
Expand Down

0 comments on commit 5016eef

Please sign in to comment.