Skip to content

Commit 4dc505f

Browse files
[BE] 이미지 반환 시 webp 확장자로 반환할 수 있도록 전환한다. (#523)
* feat: 정적파일 리사이징 로직 추가 * feat: Gif resize 기능 수정 * feat: 불필요클래스 제거 및 Content type 반환 수정 * feat: 불필요클래스 제거 및 Content type 반환 수정 * feat: 이미지 비율 메소드 수정 * feat: gif delay time 조정 * refactor: 개행 제거 * refactor: resize에서 extension 미사용으로 인한 제거 * refacotr: resize strategy 패키지 분리 * feat: webp convert strategy 구현 및 테스트 * refactor: convert 클래스 패키지 이동 * feat: webp 전환을 원하는경우 할 수 있도록 service, api 수정 * feat: image to webp 수정 * chore: 미사용 dependency 제거 * test: webp 테스트하기 위한 dependency 추가 * test: 테스트 네임 길이 수정 * feat: linux 64, arm 버전 명시 * test: 테스트 네임 수정 * refactor: Override 추가 * refactor: webp Media type 상수화 * refactor: process 정상작동 확인 메소드 분리 * refactor: 주석 제거 * refactor: @Getter 수정 * feat: ChangeWidth value object 추가 * refactor: webp 변환확인 메소드 분리 * feat: 예외 상황 handling 추가 * test: 테스트 오류 수정
1 parent 251e0ca commit 4dc505f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+866
-285
lines changed

imagestorage/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ dependencies {
3737
annotationProcessor 'org.projectlombok:lombok'
3838

3939
// image resize
40-
implementation 'com.madgag:animated-gif-lib:1.4'
40+
implementation 'com.sksamuel.scrimage:scrimage-core:4.0.31'
41+
testImplementation 'com.sksamuel.scrimage:scrimage-webp:4.0.31'
4142
}
4243

4344
tasks.named('test') {

imagestorage/src/main/java/com/woowacourse/imagestorage/application/ImageService.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.woowacourse.imagestorage.application;
22

33
import com.woowacourse.imagestorage.application.response.ImageResponse;
4+
import com.woowacourse.imagestorage.application.response.ImageSaveResponse;
5+
import com.woowacourse.imagestorage.domain.ChangeWidth;
46
import com.woowacourse.imagestorage.domain.ImageExtension;
57
import com.woowacourse.imagestorage.domain.ImageFile;
68
import com.woowacourse.imagestorage.exception.FileIOException;
@@ -17,13 +19,16 @@
1719
import org.apache.commons.io.FilenameUtils;
1820
import org.apache.commons.io.IOUtils;
1921
import org.springframework.beans.factory.annotation.Value;
22+
import org.springframework.http.MediaType;
2023
import org.springframework.stereotype.Service;
2124
import org.springframework.web.multipart.MultipartFile;
2225

2326
@Service
2427
@Slf4j
2528
public class ImageService {
2629

30+
private static final MediaType IMAGE_WEBP = new MediaType("image", "webp");
31+
2732
private final Path storageLocation;
2833
private final String imagePathPrefix;
2934

@@ -33,27 +38,28 @@ public ImageService(@Value("${file.upload-dir}") final String storageLocation,
3338
this.imagePathPrefix = imagePathPrefix;
3439
}
3540

36-
public ImageResponse storeImage(final MultipartFile image) {
41+
public ImageSaveResponse storeImage(final MultipartFile image) {
3742
try {
3843
ImageFile imageFile = ImageFile.from(image);
3944

4045
String imageFileInputName = imageFile.randomName();
4146
Path fileStorageLocation = resolvePath(imageFileInputName);
4247
Files.copy(imageFile.inputStream(), fileStorageLocation, StandardCopyOption.REPLACE_EXISTING);
43-
return new ImageResponse(imagePathPrefix + imageFileInputName);
48+
return new ImageSaveResponse(imagePathPrefix + imageFileInputName);
4449
} catch (IOException exception) {
4550
throw new FileIOException("이미지 저장 시 예외가 발생했습니다.");
4651
}
4752
}
4853

49-
public byte[] resizeImage(final String imageUrl, final int width) {
54+
public ImageResponse resizeImage(final String imageUrl, final int width, final boolean isWebp) {
5055
try {
5156
Path fileStorageLocation = resolvePath(imageUrl);
5257
File file = fileStorageLocation.toFile();
5358
ImageExtension imageExtension = ImageExtension.from(FilenameUtils.getExtension(file.getName()));
5459
byte[] originImage = IOUtils.toByteArray(new FileInputStream(file));
60+
byte[] resizedImage = imageExtension.resizeImage(originImage, new ChangeWidth(width));
5561

56-
return imageExtension.resizeImage(originImage, width);
62+
return imageByRequestToWebp(isWebp, imageExtension, resizedImage);
5763
} catch (FileNotFoundException exception) {
5864
throw new FileIONotFoundException("파일 경로에 파일이 존재하지 않습니다.");
5965
} catch (IOException exception) {
@@ -64,4 +70,12 @@ public byte[] resizeImage(final String imageUrl, final int width) {
6470
private Path resolvePath(final String imageUrl) {
6571
return storageLocation.resolve(imageUrl);
6672
}
73+
74+
private ImageResponse imageByRequestToWebp(final boolean isWebp, final ImageExtension imageExtension,
75+
final byte[] resizedImage) {
76+
if (isWebp) {
77+
return ImageResponse.of(imageExtension.convertToWebp(resizedImage), IMAGE_WEBP);
78+
}
79+
return ImageResponse.of(resizedImage, imageExtension.getContentType());
80+
}
6781
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
package com.woowacourse.imagestorage.application.response;
22

3+
import lombok.Getter;
4+
import org.springframework.http.MediaType;
5+
6+
@Getter
37
public class ImageResponse {
48

5-
private String imagePath;
9+
private byte[] bytes;
10+
private MediaType contentType;
611

712
private ImageResponse() {
813
}
914

10-
public ImageResponse(final String imagePath) {
11-
this.imagePath = imagePath;
15+
public ImageResponse(final byte[] bytes, final MediaType contentType) {
16+
this.bytes = bytes;
17+
this.contentType = contentType;
1218
}
1319

14-
public String getImagePath() {
15-
return imagePath;
20+
public static ImageResponse of(final byte[] bytes, final MediaType contentType) {
21+
return new ImageResponse(bytes, contentType);
1622
}
1723
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.woowacourse.imagestorage.application.response;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class ImageSaveResponse {
7+
8+
private String imagePath;
9+
10+
private ImageSaveResponse() {
11+
}
12+
13+
public ImageSaveResponse(final String imagePath) {
14+
this.imagePath = imagePath;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.woowacourse.imagestorage.domain;
2+
3+
import com.woowacourse.imagestorage.exception.BusinessException;
4+
import java.util.Objects;
5+
import lombok.Getter;
6+
7+
@Getter
8+
public class ChangeWidth {
9+
10+
private static final int MIN_WIDTH = 10;
11+
12+
private final int value;
13+
14+
public ChangeWidth(final int value) {
15+
checkAvaliableWidht(value);
16+
this.value = value;
17+
}
18+
19+
private void checkAvaliableWidht(final int value) {
20+
if (value < MIN_WIDTH) {
21+
throw new BusinessException("변경할 가로 길이가 작습니다.");
22+
}
23+
}
24+
25+
@Override
26+
public boolean equals(final Object o) {
27+
if (this == o) {
28+
return true;
29+
}
30+
if (o == null || getClass() != o.getClass()) {
31+
return false;
32+
}
33+
final ChangeWidth that = (ChangeWidth) o;
34+
return value == that.value;
35+
}
36+
37+
@Override
38+
public int hashCode() {
39+
return Objects.hash(value);
40+
}
41+
}

imagestorage/src/main/java/com/woowacourse/imagestorage/domain/ImageExtension.java

+25-11
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
11
package com.woowacourse.imagestorage.domain;
22

33
import com.woowacourse.imagestorage.exception.BusinessException;
4-
import com.woowacourse.imagestorage.strategy.GifImageResizeStrategy;
5-
import com.woowacourse.imagestorage.strategy.ImageResizeStrategy;
6-
import com.woowacourse.imagestorage.strategy.StaticImageResizeStrategy;
4+
import com.woowacourse.imagestorage.strategy.convert.Convert2WebpStrategy;
5+
import com.woowacourse.imagestorage.strategy.convert.Gif2WebpStrategy;
6+
import com.woowacourse.imagestorage.strategy.convert.StaticImg2WebpStrategy;
7+
import com.woowacourse.imagestorage.strategy.resize.GifImageResizeStrategy;
8+
import com.woowacourse.imagestorage.strategy.resize.ImageResizeStrategy;
9+
import com.woowacourse.imagestorage.strategy.resize.JpegImageResizeStrategy;
10+
import com.woowacourse.imagestorage.strategy.resize.PngImageResizeStrategy;
711
import java.util.Arrays;
812
import lombok.Getter;
13+
import org.springframework.http.MediaType;
914

1015
@Getter
1116
public enum ImageExtension {
1217

13-
PNG("png", new StaticImageResizeStrategy()),
14-
JPEG("jpeg", new StaticImageResizeStrategy()),
15-
JPG("jpg", new StaticImageResizeStrategy()),
16-
SVG("svg", new StaticImageResizeStrategy()),
17-
GIF("gif", new GifImageResizeStrategy()),
18+
PNG("png", MediaType.IMAGE_PNG, new PngImageResizeStrategy(), new StaticImg2WebpStrategy()),
19+
JPEG("jpeg", MediaType.IMAGE_JPEG, new JpegImageResizeStrategy(), new StaticImg2WebpStrategy()),
20+
JPG("jpg", MediaType.IMAGE_JPEG, new JpegImageResizeStrategy(), new StaticImg2WebpStrategy()),
21+
SVG("svg", new MediaType("image", "svg+xml"), new JpegImageResizeStrategy(), new StaticImg2WebpStrategy()),
22+
GIF("gif", MediaType.IMAGE_GIF, new GifImageResizeStrategy(), new Gif2WebpStrategy()),
1823
;
1924

2025
private final String extension;
26+
private final MediaType contentType;
2127
private final ImageResizeStrategy imageResizeStrategy;
28+
private final Convert2WebpStrategy convert2WebpStrategy;
2229

23-
ImageExtension(final String extension, final ImageResizeStrategy imageResizeStrategy) {
30+
ImageExtension(final String extension, final MediaType contentType, final ImageResizeStrategy imageResizeStrategy,
31+
final Convert2WebpStrategy convert2WebpStrategy) {
2432
this.extension = extension;
33+
this.contentType = contentType;
2534
this.imageResizeStrategy = imageResizeStrategy;
35+
this.convert2WebpStrategy = convert2WebpStrategy;
2636
}
2737

2838
public static ImageExtension from(final String format) {
@@ -32,8 +42,12 @@ public static ImageExtension from(final String format) {
3242
.orElseThrow(() -> new BusinessException("이미지 파일 확장자만 들어올 수 있습니다."));
3343
}
3444

35-
public byte[] resizeImage(final byte[] originBytes, final int width) {
36-
return imageResizeStrategy.resize(originBytes, width, extension);
45+
public byte[] resizeImage(final byte[] originBytes, final ChangeWidth width) {
46+
return imageResizeStrategy.resize(originBytes, width);
47+
}
48+
49+
public byte[] convertToWebp(final byte[] originBytes) {
50+
return convert2WebpStrategy.convert(originBytes);
3751
}
3852

3953
private boolean containsType(final String format) {

imagestorage/src/main/java/com/woowacourse/imagestorage/domain/ImageFile.java

-9
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,6 @@ private static void validateNullFileName(final MultipartFile multipartFile) {
6060
}
6161
}
6262

63-
public ImageFile resizeImage(final int width) {
64-
return new ImageFile(
65-
originFileName,
66-
contentType,
67-
extension,
68-
extension.resizeImage(imageBytes, width)
69-
);
70-
}
71-
7263
public InputStream inputStream() {
7364
return new ByteArrayInputStream(imageBytes);
7465
}

imagestorage/src/main/java/com/woowacourse/imagestorage/exception/ControllerAdvice.java

+12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ public ResponseEntity<Void> handleFileNotFoundException(final FileIONotFoundExce
2828
return ResponseEntity.notFound().build();
2929
}
3030

31+
@ExceptionHandler(FileConvertException.class)
32+
public ResponseEntity<ErrorResponse> handleFileConvertException(final FileConvertException e) {
33+
log.error(e.getMessage());
34+
return ResponseEntity.internalServerError().build();
35+
}
36+
37+
@ExceptionHandler(FileResizeException.class)
38+
public ResponseEntity<ErrorResponse> handleFileResizeException(final FileResizeException e) {
39+
log.error(e.getMessage());
40+
return ResponseEntity.internalServerError().build();
41+
}
42+
3143
@ExceptionHandler(Exception.class)
3244
public ResponseEntity<Void> handleException(final Exception e) {
3345
log.error("Stack Trace : {}", extractStackTrace(e));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.woowacourse.imagestorage.exception;
2+
3+
public class FileConvertException extends RuntimeException {
4+
5+
public FileConvertException(final String message) {
6+
super(message);
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.woowacourse.imagestorage.exception;
2+
3+
public class FileResizeException extends RuntimeException {
4+
5+
public FileResizeException(final String message) {
6+
super(message);
7+
}
8+
}

imagestorage/src/main/java/com/woowacourse/imagestorage/presentation/ImageController.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import com.woowacourse.imagestorage.application.ImageService;
44
import com.woowacourse.imagestorage.application.response.ImageResponse;
5+
import com.woowacourse.imagestorage.application.response.ImageSaveResponse;
56
import java.util.concurrent.TimeUnit;
67
import org.springframework.http.CacheControl;
7-
import org.springframework.http.MediaType;
88
import org.springframework.http.ResponseEntity;
99
import org.springframework.web.bind.annotation.GetMapping;
1010
import org.springframework.web.bind.annotation.PathVariable;
@@ -18,6 +18,7 @@
1818
public class ImageController {
1919

2020
private static final String DEFAULT_RESIZE_WIDTH = "500";
21+
private static final String DEFAULT_WEBP = "true";
2122
private static final int CACHE_CONTROL_MAX_AGE = 30;
2223

2324
private final ImageService imageService;
@@ -29,15 +30,18 @@ public ImageController(final ImageService imageService) {
2930
@PostMapping("/api/image-upload")
3031
public ResponseEntity<String> uploadImage(@RequestPart MultipartFile file) {
3132

32-
ImageResponse imageResponse = imageService.storeImage(file);
33-
return ResponseEntity.ok(imageResponse.getImagePath());
33+
ImageSaveResponse imageSaveResponse = imageService.storeImage(file);
34+
return ResponseEntity.ok(imageSaveResponse.getImagePath());
3435
}
3536

36-
@GetMapping(value = "/api/resize/{imageUrl}", produces = MediaType.IMAGE_JPEG_VALUE)
37+
@GetMapping("/api/resize/{imageUrl}")
3738
public ResponseEntity<byte[]> getResizeImage(@PathVariable String imageUrl,
38-
@RequestParam(required = false, defaultValue = DEFAULT_RESIZE_WIDTH) int width) {
39+
@RequestParam(required = false, defaultValue = DEFAULT_RESIZE_WIDTH) int width,
40+
@RequestParam(required = false, defaultValue = DEFAULT_WEBP) boolean webp) {
41+
ImageResponse response = imageService.resizeImage(imageUrl, width, webp);
3942
return ResponseEntity.ok()
4043
.cacheControl(CacheControl.maxAge(CACHE_CONTROL_MAX_AGE, TimeUnit.DAYS))
41-
.body(imageService.resizeImage(imageUrl, width));
44+
.contentType(response.getContentType())
45+
.body(response.getBytes());
4246
}
4347
}

imagestorage/src/main/java/com/woowacourse/imagestorage/strategy/GifImageResizeStrategy.java

-37
This file was deleted.

imagestorage/src/main/java/com/woowacourse/imagestorage/strategy/ImageResizeStrategy.java

-6
This file was deleted.

imagestorage/src/main/java/com/woowacourse/imagestorage/strategy/StaticImageResizeStrategy.java

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.woowacourse.imagestorage.strategy.convert;
2+
3+
public interface Convert2WebpStrategy {
4+
5+
byte[] convert(byte[] originBytes);
6+
}

0 commit comments

Comments
 (0)