diff --git a/api/build.gradle b/api/build.gradle index 3ad65df..9773f7c 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -5,6 +5,17 @@ bootJar { enabled = true } // 외부에서 의존하기 위한 jar로 생성하는 옵션, main이 없는 라이브러리에서는 true로 비활성화함 jar { enabled = false } +ext { + set('springCloudVersion', "2022.0.5") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + + dependencies { implementation project(':core:core-domain'); implementation project(':core:core-infra-rdb'); @@ -38,4 +49,7 @@ dependencies { // sentry implementation 'io.sentry:sentry-spring-boot-starter-jakarta:7.1.0' + + // feign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' } diff --git a/api/http/test.http b/api/http/test.http index 3e5462c..7a7dd66 100644 --- a/api/http/test.http +++ b/api/http/test.http @@ -20,3 +20,6 @@ Content-Type: application/json ### 상품 페이지 조회 GET http://localhost:8080/api/v1/items + +### 어드민 크롤링 기능 +POST http://localhost:8080/api/v1/admin/items/crawl?url=https://s.zigzag.kr/f2KRWpFiXx diff --git a/api/src/main/java/com/mm/api/domain/admin/service/AdminService.java b/api/src/main/java/com/mm/api/domain/admin/service/AdminService.java index d998338..c6a1395 100644 --- a/api/src/main/java/com/mm/api/domain/admin/service/AdminService.java +++ b/api/src/main/java/com/mm/api/domain/admin/service/AdminService.java @@ -18,22 +18,28 @@ @Transactional @RequiredArgsConstructor public class AdminService { - private ItemRepository itemRepository; - - private CrawlerService crawlerService; + private final ItemRepository itemRepository; + private final CrawlerService crawlerService; public ItemDetailResponse crawlItem(String url) { - Item savedItem = itemRepository.save(crawlerService.getZigZagItemByCrawler(url)); + Item item = crawlerService.getZigZagItemByCrawler(url); + Item savedItem = itemRepository.save(item); return getItemDetailResponseByItem(savedItem); } private ItemDetailResponse getItemDetailResponseByItem(Item savedItem) { - List images = savedItem.getItemImages().stream() - .map(ItemImage::getUrl) - .toList(); - List videos = savedItem.getItemVideos().stream() - .map(ItemVideo::getUrl) - .toList(); + List images = null; + if (savedItem.getItemImages() != null) { + images = savedItem.getItemImages().stream() + .map(ItemImage::getUrl) + .toList(); + } + List videos = null; + if (savedItem.getItemVideos() != null) { + videos = savedItem.getItemVideos().stream() + .map(ItemVideo::getUrl) + .toList(); + } return ItemDetailResponse.of(savedItem, images, videos, false); } } diff --git a/api/src/main/java/com/mm/api/domain/item/dto/response/ItemResponse.java b/api/src/main/java/com/mm/api/domain/item/dto/response/ItemResponse.java index 75015e6..a2c47a6 100644 --- a/api/src/main/java/com/mm/api/domain/item/dto/response/ItemResponse.java +++ b/api/src/main/java/com/mm/api/domain/item/dto/response/ItemResponse.java @@ -5,7 +5,6 @@ public record ItemResponse(Long id, String title, - String detail, String redirectUrl, ItemCategoryType categoryType, Integer price, @@ -17,7 +16,6 @@ public static ItemResponse of(Item item, Boolean isDib) { return new ItemResponse( item.getId(), item.getTitle(), - item.getDetail(), item.getRedirectUrl(), item.getCategoryType(), item.getPrice(), diff --git a/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemImage.java b/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemImage.java index 54f2846..079a57c 100644 --- a/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemImage.java +++ b/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemImage.java @@ -2,6 +2,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.Lob; @@ -19,6 +21,7 @@ @AllArgsConstructor public class ItemImage extends BaseEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Lob diff --git a/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemVideo.java b/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemVideo.java index a8ddd33..b17f4b4 100644 --- a/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemVideo.java +++ b/core/core-domain/src/main/java/com/mm/coredomain/domain/ItemVideo.java @@ -1,7 +1,18 @@ package com.mm.coredomain.domain; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Entity @@ -9,13 +20,14 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class ItemVideo extends BaseEntity { - @Id - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @Lob - private String url; + @Lob + private String url; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "item_id") - private Item item; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; } diff --git a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/client/ZigZagCrawlerClient.java b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/client/ZigZagCrawlerClient.java index 7c0cfd0..70a1ed9 100644 --- a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/client/ZigZagCrawlerClient.java +++ b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/client/ZigZagCrawlerClient.java @@ -1,15 +1,17 @@ package com.mm.coreinfrafeign.client; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import com.mm.coreinfrafeign.dto.requset.ZigZagCrawlerRequest; import com.mm.coreinfrafeign.dto.response.ZigZagCrawlerResponse; +@Component @FeignClient(name = "ZigZagCrawler", url = "https://87vlx0pzf0.execute-api.ap-northeast-2.amazonaws.com/crawler/zigzag") public interface ZigZagCrawlerClient { - @GetMapping + @PostMapping ZigZagCrawlerResponse call( @RequestBody ZigZagCrawlerRequest request ); diff --git a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/config/FeignConfig.java b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/config/FeignConfig.java index 6dab1a1..3ab216b 100644 --- a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/config/FeignConfig.java +++ b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/config/FeignConfig.java @@ -1,9 +1,11 @@ package com.mm.coreinfrafeign.config; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration -@EnableFeignClients("com.mm.coreinfrafeign") +@ComponentScan(basePackages = "com.mm") +@EnableFeignClients(basePackages = "com.mm") public class FeignConfig { } diff --git a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/dto/response/ZigZagCrawlerResponse.java b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/dto/response/ZigZagCrawlerResponse.java index f2e0098..224ba79 100644 --- a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/dto/response/ZigZagCrawlerResponse.java +++ b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/dto/response/ZigZagCrawlerResponse.java @@ -2,33 +2,38 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) public record ZigZagCrawlerResponse( - String type, - String pageUrl, - String siteName, - String price, - String brand, - String thumbnailUrl, - String name, - String categoryKey, - List categoryList, - List productImageList, - List managedCategoryList, - int originalPrice, - int finalPrice + @JsonProperty("type") String type, + @JsonProperty("page_url") String pageUrl, + @JsonProperty("site_name") String siteName, + @JsonProperty("price") String price, + @JsonProperty("brand") String brand, + @JsonProperty("thumbnail_url") String thumbnailUrl, + @JsonProperty("name") String name, + @JsonProperty("category_key") String categoryKey, + @JsonProperty("category_list") List categoryList, + @JsonProperty("product_image_list") List productImageList, + @JsonProperty("managed_category_list") List managedCategoryList, + @JsonProperty("original_price") int originalPrice, + @JsonProperty("final_price") int finalPrice ) { - public static record Category(int categoryId, String value) { + public static record Category(@JsonProperty("category_id") int categoryId, @JsonProperty("value") String value) { } public static record ProductImage( - String id, - String url, - String originUrl, - String pdpThumbnailUrl, - String pdpStaticImageUrl, - String imageType, - int originWidth, - int originHeight + @JsonProperty("id") String id, + @JsonProperty("url") String url, + @JsonProperty("origin_url") String originUrl, + @JsonProperty("pdp_thumbnail_url") String pdpThumbnailUrl, + @JsonProperty("pdp_static_image_url") String pdpStaticImageUrl, + @JsonProperty("image_type") String imageType, + @JsonProperty("origin_width") int originWidth, + @JsonProperty("origin_height") int originHeight ) { } } diff --git a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/service/CrawlerService.java b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/service/CrawlerService.java index bd9457b..e46c82c 100644 --- a/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/service/CrawlerService.java +++ b/core/core-infra-feign/src/main/java/com/mm/coreinfrafeign/service/CrawlerService.java @@ -3,6 +3,7 @@ import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.mm.coredomain.domain.Item; import com.mm.coredomain.domain.ItemCategoryType; @@ -14,6 +15,7 @@ import lombok.RequiredArgsConstructor; @Service +@Transactional @RequiredArgsConstructor public class CrawlerService { private final ZigZagCrawlerClient zigZagCrawlerClient; @@ -46,6 +48,6 @@ private static ItemCategoryType getCategoryType(ZigZagCrawlerResponse response) } private Integer getRefundPrice(Integer price, Integer percent) { - return (100 - percent) / 100 * price; + return price * (100 - percent) / 100; } } diff --git a/core/core-infra-qdsl/src/main/resources/data.sql b/core/core-infra-qdsl/src/main/resources/data.sql index 2d08d23..2aed5d5 100644 --- a/core/core-infra-qdsl/src/main/resources/data.sql +++ b/core/core-infra-qdsl/src/main/resources/data.sql @@ -6,19 +6,3 @@ insert into groups(id, name) values(2, 'USER_GROUP'); insert into group_permission(id, groups_id, permission_id) values(1, 1, 1); insert into group_permission(id, groups_id, permission_id) values(2, 2, 2); - -insert into item(id, detail, redirect_url, category_type, price, refund, rating, thumbnail_url) -values (1, 'test', 'https://s.zigzag.kr/3HSmoWF6Xa?af=1', 'BEAUTY', 10800, 1000, 5.0, 'https://cf.product-image.s.zigzag.kr/original/d/2023/9/21/21909_202309210952510497_81140.jpeg?width=720&height=720&quality=80&format=webp'); -insert into item_video(id, url, item_id) values (1, 'https://youtu.be/TDmzXuma4Lw?si=9RFwafiFgTUOMsvE', 1); - -insert into item(id, detail, redirect_url, category_type, price, refund, rating, thumbnail_url) -values (2, 'test', 'https://s.zigzag.kr/3HSmoWF6Xa?af=1', 'FOOD', 10800, 1000, 5.0, 'https://cf.product-image.s.zigzag.kr/original/d/2023/9/21/21909_202309210952510497_81140.jpeg?width=720&height=720&quality=80&format=webp'); -insert into item_video(id, url, item_id) values (2, 'https://youtu.be/TDmzXuma4Lw?si=9RFwafiFgTUOMsvE', 2); - -insert into item(id, detail, redirect_url, category_type, price, refund, rating, thumbnail_url) -values (3, 'test', 'https://s.zigzag.kr/3HSmoWF6Xa?af=1', 'LIFE', 10800, 1000, 5.0, 'https://cf.product-image.s.zigzag.kr/original/d/2023/9/21/21909_202309210952510497_81140.jpeg?width=720&height=720&quality=80&format=webp'); -insert into item_video(id, url, item_id) values (3, 'https://youtu.be/TDmzXuma4Lw?si=9RFwafiFgTUOMsvE', 3); - -insert into item(id, detail, redirect_url, category_type, price, refund, rating, thumbnail_url) -values (4, 'test', 'https://s.zigzag.kr/3HSmoWF6Xa?af=1', 'BEAUTY', 10800, 1000, 5.0, 'https://cf.product-image.s.zigzag.kr/original/d/2023/9/21/21909_202309210952510497_81140.jpeg?width=720&height=720&quality=80&format=webp'); -insert into item_video(id, url, item_id) values (4, 'https://youtu.be/TDmzXuma4Lw?si=9RFwafiFgTUOMsvE', 4);