Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ jobs:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

naver.client.id=${{ secrets.NAVER_ID }}
naver.client.secret=${{ secrets.NAVER_SECRET }}
EOT
shell: bash

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

naver.client.id=${{ secrets.NAVER_ID }}
naver.client.secret=${{ secrets.NAVER_SECRET }}
EOT
shell: bash

Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'org.json:json:20210307'
}

// Querydsl 설정부
Expand Down
2 changes: 2 additions & 0 deletions src/main/generated/kw/zeropick/product/domain/QProduct.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class QProduct extends EntityPathBase<Product> {

public final NumberPath<Integer> price = createNumber("price", Integer.class);

public final StringPath productLink = createString("productLink");

public final StringPath productName = createString("productName");

public final NumberPath<Integer> reviewCount = createNumber("reviewCount", Integer.class);
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/kw/zeropick/config/RestTemplateConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kw.zeropick.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kw.zeropick.product.controller;

import kw.zeropick.product.service.ProductSearchService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
@RequiredArgsConstructor
public class ProductSearchController {
private final ProductSearchService productSearchServiceService;

@PostMapping("/update-all")
public ResponseEntity<String> updateAllProducts() {
productSearchServiceService.updateAllProducts();
return ResponseEntity.ok("Product information updated successfully.");
}
}
14 changes: 14 additions & 0 deletions src/main/java/kw/zeropick/product/domain/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public class Product extends BaseEntity {

private String imageUrl;

private String productLink;

private int bookmarkCount;

private int reviewCount;
Expand Down Expand Up @@ -112,6 +114,18 @@ public void setPopularity() {
this.popularity = this.viewCount * 150 + this.bookmarkCount * 250 + this.reviewCount * 250 + starRateScore;
}

public void setProductLink(String productLink) {
this.productLink = productLink;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

public void setPrice(int price) {
this.price = price;
}



}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/kw/zeropick/product/dto/ProductDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class ProductDto {

private String imageUrl;

private String productLink; // 최저가 상품 링크 추가

private int bookmarkCount;

private int reviewCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
public interface ProductJpaRepository extends JpaRepository<Product, Long>, ProductQueryDslRepository {
@Query(value = "SELECT p FROM Product p ORDER BY p.popularity DESC")
List<Product> findTopProductsByPopularity(); // 모든 상품을 가져오되 인기순으로 정렬
List<Product> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kw.zeropick.product.service;

import kw.zeropick.product.domain.Product;

public interface ProductSearchService {
public void updateAllProducts();
public void updateProductInfo(Product product);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package kw.zeropick.product.service;

import kw.zeropick.product.domain.Product;
import kw.zeropick.product.repository.ProductJpaRepository;
import lombok.RequiredArgsConstructor;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
@RequiredArgsConstructor
public class ProductSearchServiceImpl implements ProductSearchService{
private final ProductJpaRepository productRepository;
private final RestTemplate restTemplate;

private static final String NAVER_API_URL = "https://openapi.naver.com/v1/search/shop.json?query=";
@Value("${naver.client.id}")
private String clientId;

@Value("${naver.client.secret}")
private String clientSecret;

@Override
@Transactional
public void updateAllProducts() {
List<Product> products = productRepository.findAll();

for (Product product : products) {
try {
updateProductInfo(product);
Thread.sleep(200); // 0.2초 대기 (초당 5건 제한)
} catch (Exception e) {
System.err.println("Error updating product: " + product.getProductName());
e.printStackTrace();
}
}

}

@Transactional
public void updateProductInfo(Product product) {
String query = product.getProductName();
String apiUrl = NAVER_API_URL + query + "&sort=asc";

HttpHeaders headers = new HttpHeaders();
headers.set("X-Naver-Client-Id", clientId);
headers.set("X-Naver-Client-Secret", clientSecret);
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, String.class);

if (response.getBody() == null) {
System.err.println("API 응답이 null입니다: " + product.getProductName());
return;
}

JSONObject jsonResponse = new JSONObject(response.getBody());

if (!jsonResponse.has("items")) {
System.err.println("API 응답에 'items' 필드 없음: " + response.getBody());
return;
}

JSONArray items = jsonResponse.getJSONArray("items");

if (items.length() == 0) {
System.err.println("검색된 상품 없음: " + product.getProductName());
return;
}

JSONObject firstItem = items.getJSONObject(0); // 최저가 상품

// 필수 값 검증
if (!firstItem.has("link") || !firstItem.has("image") || !firstItem.has("lprice")) {
System.err.println("상품 데이터에 필수 필드 없음: " + firstItem.toString());
return;
}

product.setProductLink(firstItem.getString("link"));
product.setImageUrl(firstItem.getString("image"));
product.setPrice(Integer.parseInt(firstItem.getString("lprice"))); // 가격이 문자열일 경우 처리

productRepository.save(product);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ private ProductDto toProductDto(Product product, Member member) {
.starRate(product.getStarRate())
.viewCount(product.getViewCount())
.imageUrl(product.getImageUrl())
.productLink(product.getProductLink())
.bookmarkCount(product.getBookmarkCount())
.reviewCount(product.getReviewCount())
.bookmarked(isBookmarked)
Expand Down