From 0a011895360e6f8b3d0e57fceaa2611c9a5ddbab Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 21:16:08 +0900 Subject: [PATCH 01/19] feat: add NCP related settings (#12) * feat: add dependency io.awspring.cloud:spring-cloud-starter-aws:2.4.4 * feat: create NCP configuration --- photo-service/build.gradle.kts | 2 +- .../java/kr/mafoo/photo/config/NcpConfig.java | 35 +++++++++++++++++++ .../src/main/resources/application.yaml | 13 +++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/config/NcpConfig.java diff --git a/photo-service/build.gradle.kts b/photo-service/build.gradle.kts index 500fe29..318d900 100644 --- a/photo-service/build.gradle.kts +++ b/photo-service/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation("org.projectlombok:lombok:1.18.32") annotationProcessor("org.projectlombok:lombok:1.18.32") implementation("com.github.f4b6a3:ulid-creator:5.2.3") - + implementation("io.awspring.cloud:spring-cloud-starter-aws:2.4.4") } tasks.withType { diff --git a/photo-service/src/main/java/kr/mafoo/photo/config/NcpConfig.java b/photo-service/src/main/java/kr/mafoo/photo/config/NcpConfig.java new file mode 100644 index 0000000..0de7d61 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/config/NcpConfig.java @@ -0,0 +1,35 @@ +package kr.mafoo.photo.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class NcpConfig { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Value("${cloud.aws.s3.endpoint}") + private String endPoint; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey,secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region)) + .build(); + } +} \ No newline at end of file diff --git a/photo-service/src/main/resources/application.yaml b/photo-service/src/main/resources/application.yaml index e96c8d7..fd8458b 100644 --- a/photo-service/src/main/resources/application.yaml +++ b/photo-service/src/main/resources/application.yaml @@ -11,3 +11,16 @@ spring: enabled: true user: ${MYSQL_USERNAME} password: ${MYSQL_PASSWORD} + +cloud: + aws: + credentials: + access-key: ${NCP_ACCESS_KEY} + secret-key: ${NCP_SECRET_KEY} + stack: + auto: false + region: + static: kr-standard + s3: + endpoint: https://kr.object.ncloudstorage.com + bucket: ${NCP_BUCKET} \ No newline at end of file From c1a934bee37d3d9d1c9b572185fc5d69663bf8ab Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:05:25 +0900 Subject: [PATCH 02/19] feat: add webclient settings (#12) --- .../java/kr/mafoo/photo/config/WebFluxConfig.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/photo-service/src/main/java/kr/mafoo/photo/config/WebFluxConfig.java b/photo-service/src/main/java/kr/mafoo/photo/config/WebFluxConfig.java index 7549765..fd49fbe 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/config/WebFluxConfig.java +++ b/photo-service/src/main/java/kr/mafoo/photo/config/WebFluxConfig.java @@ -1,8 +1,10 @@ package kr.mafoo.photo.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; @EnableWebFlux @@ -12,4 +14,15 @@ public class WebFluxConfig implements WebFluxConfigurer { public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { configurer.addCustomResolver(new MemberIdParameterResolver()); } + + @Bean("externalWebClient") + public WebClient externalServiceWebClient() { + return WebClient.builder() + .codecs(clientCodecConfigurer -> { + clientCodecConfigurer + .defaultCodecs() + .maxInMemorySize(16 * 1024 * 1024); // 16MB + }) + .build(); + } } From 2506ecc344b789350c3cb5ad0e58e70eee27939b Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:06:13 +0900 Subject: [PATCH 03/19] feat: create photo brand type enum (#12) --- .../java/kr/mafoo/photo/domain/BrandType.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java b/photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java new file mode 100644 index 0000000..1e2e7e9 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/BrandType.java @@ -0,0 +1,32 @@ +package kr.mafoo.photo.domain; + +public enum BrandType { + LIFE_FOUR_CUTS("https://api.life4cut.net/"), + PHOTOISM("https://qr.seobuk.kr/"), + HARU_FILM( "http://haru6.mx2.co.kr/"), + DONT_LOOK_UP("https://x.dontlxxkup.kr/"), + ; + + private String urlFormat; + + private BrandType(String urlFormat) { + this.urlFormat = urlFormat; + } + + public String getUrlFormat() { + return urlFormat; + } + + public boolean matches(String qrUrl) { + return qrUrl.startsWith(this.urlFormat); + } + + public static BrandType matchBrandType(String qrUrl) { + for (BrandType brandType : BrandType.values()) { + if (brandType.matches(qrUrl)) { + return brandType; + } + } + return null; + } +} From 0d4146c83ab6026751f10015726a3c8d866e4175 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:07:46 +0900 Subject: [PATCH 04/19] feat: create photo brand related exception (#12) * feat: add PHOTO_BRAND_NOT_EXISTS in ErrorCode * feat: create PhotoBrandNotExistsException --- .../src/main/java/kr/mafoo/photo/exception/ErrorCode.java | 1 + .../photo/exception/PhotoBrandNotExistsException.java | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/exception/PhotoBrandNotExistsException.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java index 5c9d304..0f9fd43 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java @@ -8,6 +8,7 @@ public enum ErrorCode { ALBUM_NOT_FOUND("AE0001", "앨범을 찾을 수 없습니다"), PHOTO_NOT_FOUND("PE0001", "사진을 찾을 수 없습니다"), + PHOTO_BRAND_NOT_EXISTS("PE002", "사진 브랜드가 존재하지 않습니다") ; private final String code; diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoBrandNotExistsException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoBrandNotExistsException.java new file mode 100644 index 0000000..59baed9 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoBrandNotExistsException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class PhotoBrandNotExistsException extends DomainException { + public PhotoBrandNotExistsException() { + super(ErrorCode.PHOTO_BRAND_NOT_EXISTS); + } +} From 040d083dfae07764f82555b70a04d17df20b8835 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:48:09 +0900 Subject: [PATCH 05/19] feat: create ncp object storage service (#12) --- .../photo/service/ObjectStorageService.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java b/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java new file mode 100644 index 0000000..8381fd9 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/ObjectStorageService.java @@ -0,0 +1,48 @@ +package kr.mafoo.photo.service; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Mono; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ObjectStorageService { + + private final AmazonS3Client amazonS3Client; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + public Mono uploadFile(byte[] fileByte) { + String keyName = "/" + UUID.randomUUID(); + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(fileByte.length); + objectMetadata.setContentType("image/jpeg"); + + return Mono.fromCallable(() -> { + try (InputStream inputStream = new ByteArrayInputStream(fileByte)) { + amazonS3Client.putObject( + new PutObjectRequest(bucketName, keyName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + + return "https://kr.object.ncloudstorage.com/" + bucketName + "/" + keyName; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + +} \ No newline at end of file From 2caf284bd117a93f5621439a2ae473ff84107f78 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:51:00 +0900 Subject: [PATCH 06/19] feat: create qr service (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement getFileAsByte * feat: implement '인생네컷' brand qr service --- .../kr/mafoo/photo/service/QrService.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/service/QrService.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java new file mode 100644 index 0000000..7d636d4 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -0,0 +1,64 @@ +package kr.mafoo.photo.service; + +import kr.mafoo.photo.domain.BrandType; +import kr.mafoo.photo.exception.PhotoBrandNotExistsException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.util.Optional; + +@RequiredArgsConstructor +@Service +public class QrService { + + private final WebClient externalWebClient; + + protected Mono getFileFromQrUrl(String qrUrl) { + BrandType brandType = Optional.ofNullable(BrandType.matchBrandType(qrUrl)) + .orElseThrow(PhotoBrandNotExistsException::new); + + return switch (brandType) { + case LIFE_FOUR_CUTS -> getLifeFourCutsFiles(qrUrl); + case PHOTOISM -> null; + case HARU_FILM -> null; + case DONT_LOOK_UP -> null; + }; + + } + + private Mono getLifeFourCutsFiles(String qrUrl) { + + return externalWebClient + .get() + .uri(qrUrl) + .retrieve() + .toBodilessEntity() + .flatMap(response -> { + URI redirectUri = response.getHeaders().getLocation(); + if (redirectUri == null) { + throw new RuntimeException("No redirection URL found"); + } else { + // TODO : 추후 비디오 URL 추가 예정 + // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); + + String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); + return getFileAsByte(imageUrl); + } + }); + } + + private Mono getFileAsByte(String url) { + return externalWebClient + .get() + .uri(url) + .accept(MediaType.APPLICATION_OCTET_STREAM) + .retrieve() + .bodyToMono(byte[].class); + } + + +} From 58992635e3854bb7ca15541b59e66708deb3e4ec Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Wed, 3 Jul 2024 23:51:49 +0900 Subject: [PATCH 07/19] feat: implement create new photo api (#12) --- .../kr/mafoo/photo/controller/PhotoController.java | 6 +++--- .../java/kr/mafoo/photo/service/PhotoService.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java b/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java index 79dad1c..cadc764 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/PhotoController.java @@ -31,9 +31,9 @@ public Mono createPhoto( String memberId, PhotoCreateRequest request ){ - return Mono.just( - new PhotoResponse("test_photo_id", "photo_url", null) - ); + return photoService + .createNewPhoto(request.qrUrl(), memberId) + .map(PhotoResponse::fromEntity); } @Override diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java index b9d4939..541a7eb 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java @@ -5,6 +5,7 @@ import kr.mafoo.photo.exception.PhotoNotFoundException; import kr.mafoo.photo.repository.AlbumRepository; import kr.mafoo.photo.repository.PhotoRepository; +import kr.mafoo.photo.util.IdGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; @@ -16,6 +17,19 @@ public class PhotoService { private final PhotoRepository photoRepository; private final AlbumRepository albumRepository; + private final QrService qrService; + private final ObjectStorageService objectStorageService; + + public Mono createNewPhoto(String qrUrl, String requestMemberId) { + return qrService + .getFileFromQrUrl(qrUrl) + .flatMap(objectStorageService::uploadFile) + .flatMap(photoUrl -> { + PhotoEntity photoEntity = PhotoEntity.newPhoto(IdGenerator.generate(), photoUrl, requestMemberId); + return photoRepository.save(photoEntity); + }); + } + public Flux findAllByAlbumId(String albumId, String requestMemberId) { return albumRepository .findById(albumId) From 208f9471caec7ac37cca2aaf50b31937c8ff69bc Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 00:41:42 +0900 Subject: [PATCH 08/19] refactor: create getRedirectUri function (#12) --- .../kr/mafoo/photo/service/QrService.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 7d636d4..5185707 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -32,9 +32,20 @@ protected Mono getFileFromQrUrl(String qrUrl) { private Mono getLifeFourCutsFiles(String qrUrl) { + return getRedirectUri(qrUrl) + .flatMap(redirectUri -> { + // TODO : 추후 비디오 URL 추가 예정 + // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); + + String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); + return getFileAsByte(imageUrl); + }); + } + + private Mono getRedirectUri(String url) { return externalWebClient .get() - .uri(qrUrl) + .uri(url) .retrieve() .toBodilessEntity() .flatMap(response -> { @@ -42,11 +53,7 @@ private Mono getLifeFourCutsFiles(String qrUrl) { if (redirectUri == null) { throw new RuntimeException("No redirection URL found"); } else { - // TODO : 추후 비디오 URL 추가 예정 - // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); - - String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); - return getFileAsByte(imageUrl); + return Mono.just(redirectUri); } }); } From 38ca11ff577b4220fff0dcebf350ec876cbd9760 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 01:28:48 +0900 Subject: [PATCH 09/19] feat: create getHaruFilmFiles function (#12) --- .../java/kr/mafoo/photo/service/QrService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 5185707..6bc19ca 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -3,6 +3,7 @@ import kr.mafoo.photo.domain.BrandType; import kr.mafoo.photo.exception.PhotoBrandNotExistsException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; @@ -11,6 +12,7 @@ import java.net.URI; import java.util.Optional; +@Slf4j @RequiredArgsConstructor @Service public class QrService { @@ -24,7 +26,7 @@ protected Mono getFileFromQrUrl(String qrUrl) { return switch (brandType) { case LIFE_FOUR_CUTS -> getLifeFourCutsFiles(qrUrl); case PHOTOISM -> null; - case HARU_FILM -> null; + case HARU_FILM -> getHaruFilmFiles(qrUrl); case DONT_LOOK_UP -> null; }; @@ -42,6 +44,19 @@ private Mono getLifeFourCutsFiles(String qrUrl) { }); } + private Mono getHaruFilmFiles(String qrUrl) { + + String albumCode = qrUrl.split("/@")[1]; + + String baseUrl = "http://haru6.mx2.co.kr/base_api?command=albumdn&albumCode="; + String imageUrl = baseUrl + albumCode + "&type=photo&file_name=output.jpg&max=10&limit=+24 hours"; + + // TODO : 추후 비디오 URL 추가 예정 + // String videoUrl = baseUrl + albumCode + "&type=video&max=10&limit=+24 hours"; + + return getFileAsByte(imageUrl); + } + private Mono getRedirectUri(String url) { return externalWebClient .get() From 0c64c7a5bbc282320a5fbf0a6adb2858ba02c6c5 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 01:29:21 +0900 Subject: [PATCH 10/19] feat: create getDontLookUpFiles function (#12) --- .../java/kr/mafoo/photo/service/QrService.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 6bc19ca..40470be 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -27,7 +27,7 @@ protected Mono getFileFromQrUrl(String qrUrl) { case LIFE_FOUR_CUTS -> getLifeFourCutsFiles(qrUrl); case PHOTOISM -> null; case HARU_FILM -> getHaruFilmFiles(qrUrl); - case DONT_LOOK_UP -> null; + case DONT_LOOK_UP -> getDontLookUpFiles(qrUrl); }; } @@ -57,6 +57,20 @@ private Mono getHaruFilmFiles(String qrUrl) { return getFileAsByte(imageUrl); } + private Mono getDontLookUpFiles(String qrUrl) { + + String imageName = qrUrl.split("/")[4]; + + String baseUrl = "https://x.dontlxxkup.kr/uploads/"; + String imageUrl = baseUrl + imageName; + + // TODO : 추후 비디오 URL 추가 예정 + // String videoName = imageName.replace("image", "video").replace(".jpg", ".mp4"); + // String videoUrl = baseUrl + videoName; + + return getFileAsByte(imageUrl); + } + private Mono getRedirectUri(String url) { return externalWebClient .get() From 8f07519769ff90b5b8480471afea18e8acced4b2 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 19:47:19 +0900 Subject: [PATCH 11/19] feat: create getPhotoismFiles function (#12) --- .../kr/mafoo/photo/service/QrService.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 40470be..46b30c8 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -10,6 +10,8 @@ import reactor.core.publisher.Mono; import java.net.URI; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; @Slf4j @@ -25,7 +27,7 @@ protected Mono getFileFromQrUrl(String qrUrl) { return switch (brandType) { case LIFE_FOUR_CUTS -> getLifeFourCutsFiles(qrUrl); - case PHOTOISM -> null; + case PHOTOISM -> getPhotoismFiles(qrUrl); case HARU_FILM -> getHaruFilmFiles(qrUrl); case DONT_LOOK_UP -> getDontLookUpFiles(qrUrl); }; @@ -36,17 +38,39 @@ private Mono getLifeFourCutsFiles(String qrUrl) { return getRedirectUri(qrUrl) .flatMap(redirectUri -> { + String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); + // TODO : 추후 비디오 URL 추가 예정 // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); - String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); return getFileAsByte(imageUrl); }); } - private Mono getHaruFilmFiles(String qrUrl) { + private Mono getPhotoismFiles(String qrUrl) { + return getRedirectUri(qrUrl) + .flatMap(redirectUri -> { + String uid = extractValueFromUrl(redirectUri.toString(), "u="); + + return externalWebClient + .post() + .uri("https://cmsapi.seobuk.kr/v1/etc/seq/resource") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(Map.of("uid", uid, "appUserId", null)) + .retrieve() + .bodyToMono(LinkedHashMap.class) + .flatMap(responseBody -> { + LinkedHashMap content = (LinkedHashMap) responseBody.get("content"); + LinkedHashMap fileInfo = (LinkedHashMap) content.get("fileInfo"); + String imageUrl = (String) fileInfo.get("picFile.path"); + + return getFileAsByte(imageUrl); + }); + }); + } - String albumCode = qrUrl.split("/@")[1]; + private Mono getHaruFilmFiles(String qrUrl) { + String albumCode = extractValueFromUrl(qrUrl, "/@"); String baseUrl = "http://haru6.mx2.co.kr/base_api?command=albumdn&albumCode="; String imageUrl = baseUrl + albumCode + "&type=photo&file_name=output.jpg&max=10&limit=+24 hours"; @@ -58,8 +82,7 @@ private Mono getHaruFilmFiles(String qrUrl) { } private Mono getDontLookUpFiles(String qrUrl) { - - String imageName = qrUrl.split("/")[4]; + String imageName = extractValueFromUrl(qrUrl, ".kr/"); String baseUrl = "https://x.dontlxxkup.kr/uploads/"; String imageUrl = baseUrl + imageName; @@ -87,6 +110,10 @@ private Mono getRedirectUri(String url) { }); } + private String extractValueFromUrl(String url, String delimiter) { + return url.split(delimiter)[1]; + } + private Mono getFileAsByte(String url) { return externalWebClient .get() From 4d17b883937955177ee66e45fadb451739d13eb9 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 19:50:23 +0900 Subject: [PATCH 12/19] refactor: change qr service's getRedirectUri return type (#12) --- .../src/main/java/kr/mafoo/photo/service/QrService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 46b30c8..2e6f0c7 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -38,7 +38,7 @@ private Mono getLifeFourCutsFiles(String qrUrl) { return getRedirectUri(qrUrl) .flatMap(redirectUri -> { - String imageUrl = redirectUri.toString().replace("index.html", "image.jpg"); + String imageUrl = redirectUri.replace("index.html", "image.jpg"); // TODO : 추후 비디오 URL 추가 예정 // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); @@ -50,7 +50,7 @@ private Mono getLifeFourCutsFiles(String qrUrl) { private Mono getPhotoismFiles(String qrUrl) { return getRedirectUri(qrUrl) .flatMap(redirectUri -> { - String uid = extractValueFromUrl(redirectUri.toString(), "u="); + String uid = extractValueFromUrl(redirectUri, "u="); return externalWebClient .post() @@ -94,7 +94,7 @@ private Mono getDontLookUpFiles(String qrUrl) { return getFileAsByte(imageUrl); } - private Mono getRedirectUri(String url) { + private Mono getRedirectUri(String url) { return externalWebClient .get() .uri(url) @@ -105,7 +105,7 @@ private Mono getRedirectUri(String url) { if (redirectUri == null) { throw new RuntimeException("No redirection URL found"); } else { - return Mono.just(redirectUri); + return Mono.just(redirectUri.toString()); } }); } From 6c302061a8e640413d09cc01faa0e5e7e87b2788 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 20:29:18 +0900 Subject: [PATCH 13/19] feat: create PhotoQrUrlExpiredException (#12) --- .../src/main/java/kr/mafoo/photo/exception/ErrorCode.java | 3 ++- .../mafoo/photo/exception/PhotoQrUrlExpiredException.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/exception/PhotoQrUrlExpiredException.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java index 0f9fd43..c139982 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java @@ -8,7 +8,8 @@ public enum ErrorCode { ALBUM_NOT_FOUND("AE0001", "앨범을 찾을 수 없습니다"), PHOTO_NOT_FOUND("PE0001", "사진을 찾을 수 없습니다"), - PHOTO_BRAND_NOT_EXISTS("PE002", "사진 브랜드가 존재하지 않습니다") + PHOTO_BRAND_NOT_EXISTS("PE002", "사진 브랜드가 존재하지 않습니다"), + PHOTO_QR_URL_EXPIRED("PE003", "사진 저장을 위한 QR URL이 만료되었습니다"), ; private final String code; diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoQrUrlExpiredException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoQrUrlExpiredException.java new file mode 100644 index 0000000..b29a30c --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/PhotoQrUrlExpiredException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class PhotoQrUrlExpiredException extends DomainException{ + public PhotoQrUrlExpiredException() { + super(ErrorCode.PHOTO_QR_URL_EXPIRED); + } +} From af4f4eed2b3e0fa82b12ded938385fb40eba33da Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 20:29:53 +0900 Subject: [PATCH 14/19] refactor: add PhotoQrUrlExpiredException in qr service (#12) --- .../kr/mafoo/photo/service/QrService.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index 2e6f0c7..de3bfd8 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -2,6 +2,7 @@ import kr.mafoo.photo.domain.BrandType; import kr.mafoo.photo.exception.PhotoBrandNotExistsException; +import kr.mafoo.photo.exception.PhotoQrUrlExpiredException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -44,7 +45,8 @@ private Mono getLifeFourCutsFiles(String qrUrl) { // String videoUrl = redirectUri.toString().replace("index.html", "video.mp4"); return getFileAsByte(imageUrl); - }); + }) + .onErrorMap(e -> new PhotoQrUrlExpiredException()); } private Mono getPhotoismFiles(String qrUrl) { @@ -66,7 +68,8 @@ private Mono getPhotoismFiles(String qrUrl) { return getFileAsByte(imageUrl); }); - }); + }) + .onErrorMap(e -> new PhotoQrUrlExpiredException()); } private Mono getHaruFilmFiles(String qrUrl) { @@ -78,7 +81,8 @@ private Mono getHaruFilmFiles(String qrUrl) { // TODO : 추후 비디오 URL 추가 예정 // String videoUrl = baseUrl + albumCode + "&type=video&max=10&limit=+24 hours"; - return getFileAsByte(imageUrl); + return getFileAsByte(imageUrl) + .onErrorMap(e -> new PhotoQrUrlExpiredException()); } private Mono getDontLookUpFiles(String qrUrl) { @@ -91,7 +95,14 @@ private Mono getDontLookUpFiles(String qrUrl) { // String videoName = imageName.replace("image", "video").replace(".jpg", ".mp4"); // String videoUrl = baseUrl + videoName; - return getFileAsByte(imageUrl); + return getRedirectUri(qrUrl) + .flatMap(redirectUri -> { + if (redirectUri.endsWith("/delete")) { + return Mono.error(new PhotoQrUrlExpiredException()); + } else { + return getFileAsByte(imageUrl); + } + }); } private Mono getRedirectUri(String url) { From 0549810629c6778cf07ac88e3132b841e50cd13b Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 20:36:22 +0900 Subject: [PATCH 15/19] feat: create RedirectUriNotFoundException (#12) --- .../src/main/java/kr/mafoo/photo/exception/ErrorCode.java | 3 +++ .../photo/exception/RedirectUriNotFoundException.java | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/exception/RedirectUriNotFoundException.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java index c139982..063c1b0 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/ErrorCode.java @@ -6,6 +6,9 @@ @Getter @RequiredArgsConstructor public enum ErrorCode { + + REDIRECT_URI_NOT_FOUND("EX001", "리다이렉트 URI를 찾을 수 없습니다"), + ALBUM_NOT_FOUND("AE0001", "앨범을 찾을 수 없습니다"), PHOTO_NOT_FOUND("PE0001", "사진을 찾을 수 없습니다"), PHOTO_BRAND_NOT_EXISTS("PE002", "사진 브랜드가 존재하지 않습니다"), diff --git a/photo-service/src/main/java/kr/mafoo/photo/exception/RedirectUriNotFoundException.java b/photo-service/src/main/java/kr/mafoo/photo/exception/RedirectUriNotFoundException.java new file mode 100644 index 0000000..4432609 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/exception/RedirectUriNotFoundException.java @@ -0,0 +1,7 @@ +package kr.mafoo.photo.exception; + +public class RedirectUriNotFoundException extends DomainException { + public RedirectUriNotFoundException() { + super(ErrorCode.REDIRECT_URI_NOT_FOUND); + } +} From e3b9e731d8d7d18b44c6d416783a8ca9ab16062b Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 20:37:09 +0900 Subject: [PATCH 16/19] refactor: add RedirectUriNotFoundException in qr service (#12) --- .../src/main/java/kr/mafoo/photo/service/QrService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index de3bfd8..a8ed5c7 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -3,6 +3,7 @@ import kr.mafoo.photo.domain.BrandType; import kr.mafoo.photo.exception.PhotoBrandNotExistsException; import kr.mafoo.photo.exception.PhotoQrUrlExpiredException; +import kr.mafoo.photo.exception.RedirectUriNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -114,7 +115,7 @@ private Mono getRedirectUri(String url) { .flatMap(response -> { URI redirectUri = response.getHeaders().getLocation(); if (redirectUri == null) { - throw new RuntimeException("No redirection URL found"); + return Mono.error(new RedirectUriNotFoundException()); } else { return Mono.just(redirectUri.toString()); } From 8de7d1a19be1d4c64b908bccd9c412f20ee6dded Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 21:11:42 +0900 Subject: [PATCH 17/19] feat: add db migration v3 (#12) * feat: add brand column in photo table --- .../main/resources/db/migration/V3__addBrandInPhotoTable.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 photo-service/src/main/resources/db/migration/V3__addBrandInPhotoTable.sql diff --git a/photo-service/src/main/resources/db/migration/V3__addBrandInPhotoTable.sql b/photo-service/src/main/resources/db/migration/V3__addBrandInPhotoTable.sql new file mode 100644 index 0000000..9e5a4b4 --- /dev/null +++ b/photo-service/src/main/resources/db/migration/V3__addBrandInPhotoTable.sql @@ -0,0 +1,2 @@ +ALTER TABLE `photo` + ADD `brand` VARCHAR(255) NOT NULL COMMENT '브랜드' after `url`; \ No newline at end of file From 31e3f3cd0799e43744f257cb888f2544c5bee15f Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 21:12:30 +0900 Subject: [PATCH 18/19] feat: add brand column in photo entity (#12) --- .../src/main/java/kr/mafoo/photo/domain/PhotoEntity.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java b/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java index c33fed8..35db5ef 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java +++ b/photo-service/src/main/java/kr/mafoo/photo/domain/PhotoEntity.java @@ -23,6 +23,9 @@ public class PhotoEntity implements Persistable { @Column("url") private String photoUrl; + @Column("brand") + private BrandType brand; + @Column("owner_member_id") private String ownerMemberId; @@ -64,10 +67,11 @@ public PhotoEntity updateAlbumId(String albumId) { return this; } - public static PhotoEntity newPhoto(String photoId, String photoUrl, String ownerMemberId) { + public static PhotoEntity newPhoto(String photoId, String photoUrl, BrandType brandType, String ownerMemberId) { PhotoEntity photo = new PhotoEntity(); photo.photoId = photoId; photo.photoUrl = photoUrl; + photo.brand = brandType; photo.ownerMemberId = ownerMemberId; photo.isNew = true; return photo; From 4420b2bafe31634a58817aee8182e5e805cc6060 Mon Sep 17 00:00:00 2001 From: Gyoungmin Kim Date: Thu, 4 Jul 2024 21:13:57 +0900 Subject: [PATCH 19/19] fix: change create new photo api for newly created brand column (#12) --- .../controller/dto/response/PhotoResponse.java | 5 +++++ .../java/kr/mafoo/photo/service/PhotoService.java | 12 +++++++----- .../java/kr/mafoo/photo/service/QrService.java | 14 +++++++++----- .../java/kr/mafoo/photo/service/dto/FileDto.java | 9 +++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java diff --git a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java index a70a1fa..3de86f7 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java +++ b/photo-service/src/main/java/kr/mafoo/photo/controller/dto/response/PhotoResponse.java @@ -1,6 +1,7 @@ package kr.mafoo.photo.controller.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.photo.domain.BrandType; import kr.mafoo.photo.domain.PhotoEntity; @Schema(description = "사진 응답") @@ -11,6 +12,9 @@ public record PhotoResponse( @Schema(description = "사진 URL", example = "photo_url") String photoUrl, + @Schema(description = "사진 브랜드", example = "LIFE_FOUR_CUTS") + BrandType brand, + @Schema(description = "앨범 ID", example = "test_album_id") String albumId ) { @@ -20,6 +24,7 @@ public static PhotoResponse fromEntity( return new PhotoResponse( entity.getPhotoId(), entity.getPhotoUrl(), + entity.getBrand(), entity.getAlbumId() ); } diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java index 541a7eb..b180b13 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/PhotoService.java @@ -1,5 +1,6 @@ package kr.mafoo.photo.service; +import kr.mafoo.photo.domain.BrandType; import kr.mafoo.photo.domain.PhotoEntity; import kr.mafoo.photo.exception.AlbumNotFoundException; import kr.mafoo.photo.exception.PhotoNotFoundException; @@ -23,11 +24,12 @@ public class PhotoService { public Mono createNewPhoto(String qrUrl, String requestMemberId) { return qrService .getFileFromQrUrl(qrUrl) - .flatMap(objectStorageService::uploadFile) - .flatMap(photoUrl -> { - PhotoEntity photoEntity = PhotoEntity.newPhoto(IdGenerator.generate(), photoUrl, requestMemberId); - return photoRepository.save(photoEntity); - }); + .flatMap(fileDto -> objectStorageService.uploadFile(fileDto.fileByte()) + .flatMap(photoUrl -> { + PhotoEntity photoEntity = PhotoEntity.newPhoto(IdGenerator.generate(), photoUrl, fileDto.type(), requestMemberId); + return photoRepository.save(photoEntity); + }) + ); } public Flux findAllByAlbumId(String albumId, String requestMemberId) { diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java index a8ed5c7..6c1e099 100644 --- a/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java +++ b/photo-service/src/main/java/kr/mafoo/photo/service/QrService.java @@ -4,6 +4,7 @@ import kr.mafoo.photo.exception.PhotoBrandNotExistsException; import kr.mafoo.photo.exception.PhotoQrUrlExpiredException; import kr.mafoo.photo.exception.RedirectUriNotFoundException; +import kr.mafoo.photo.service.dto.FileDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -23,17 +24,20 @@ public class QrService { private final WebClient externalWebClient; - protected Mono getFileFromQrUrl(String qrUrl) { + public Mono getFileFromQrUrl(String qrUrl) { BrandType brandType = Optional.ofNullable(BrandType.matchBrandType(qrUrl)) .orElseThrow(PhotoBrandNotExistsException::new); return switch (brandType) { - case LIFE_FOUR_CUTS -> getLifeFourCutsFiles(qrUrl); - case PHOTOISM -> getPhotoismFiles(qrUrl); - case HARU_FILM -> getHaruFilmFiles(qrUrl); - case DONT_LOOK_UP -> getDontLookUpFiles(qrUrl); + case LIFE_FOUR_CUTS -> createFileDto(brandType, getLifeFourCutsFiles(qrUrl)); + case PHOTOISM -> createFileDto(brandType, getPhotoismFiles(qrUrl)); + case HARU_FILM -> createFileDto(brandType, getHaruFilmFiles(qrUrl)); + case DONT_LOOK_UP -> createFileDto(brandType, getDontLookUpFiles(qrUrl)); }; + } + private Mono createFileDto(BrandType brandType, Mono fileMono) { + return fileMono.map(file -> new FileDto(brandType, file)); } private Mono getLifeFourCutsFiles(String qrUrl) { diff --git a/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java b/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java new file mode 100644 index 0000000..5dff696 --- /dev/null +++ b/photo-service/src/main/java/kr/mafoo/photo/service/dto/FileDto.java @@ -0,0 +1,9 @@ +package kr.mafoo.photo.service.dto; + +import kr.mafoo.photo.domain.BrandType; + +public record FileDto ( + BrandType type, + byte[] fileByte +) { +}