[관심 상품 등록하기]
▶ 요구 조건 살펴보기
→ 상품을 검색한 후, 등록 버튼을 눌렀을때 관심 상품이 생성되어야한다.
→ 검색 결과에서 제목,이미지, 링크, 최저가를 가져오면 된다.
▶ Dto 클래스 만들기
→ ProductRequestDto.java : 관심상품을 등록할 때 필요한 녀석
@Getter
public class ProductRequestDto {
//title, link, image, lprice
private String title;
private String link;
private String image;
private int lprice;
}
→ ProductMypriceRequestDto.java : 내가 설정한 관심가격을 변경하는 기능
@Getter
public class ProductMypriceRequestDto {
private int myprice;
}
▶ Product 클래스 개선하기
→ Product - 관심 상품 등록하기 : 생성자 보기!
@Getter // get 함수를 일괄적으로 만들어줍니다.
@NoArgsConstructor // 기본 생성자를 만들어줍니다.
@Entity // DB 테이블 역할을 합니다.
public class Product extends Timestamped{
// ID가 자동으로 생성 및 증가합니다.
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;
// 반드시 값을 가지도록 합니다.
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String image;
@Column(nullable = false)
private String link;
@Column(nullable = false)
private int lprice;
@Column(nullable = false)
private int myprice;
// 관심 상품 생성 시 이용합니다.
public Product(ProductRequestDto requestDto) {
this.title = requestDto.getTitle();
this.image = requestDto.getImage();
this.link = requestDto.getLink();
this.lprice = requestDto.getLprice();
this.myprice = 0; //설정을 안했을 때, default
}
// 관심 가격 변경 시 이용합니다.
public void update(ProductMypriceRequestDto requestDto) {
this.myprice = requestDto.getMyprice();
}
}
▶ ProductService 만들기
→ service 패키지 생성 - ProductService 생성
→ ProductService.java : 관심 가격 변경하기 -> 원래 myprice의 default가 0이므로!
@RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
@Service // 서비스임을 선언합니다.
public class ProductService {
private final ProductRepository productRepository;
@Transactional // 메소드 동작이 SQL 쿼리문임을 선언합니다.
public Long update(Long id, ProductMypriceRequestDto requestDto) {
Product product = productRepository.findById(id).orElseThrow(
() -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
);
product.update(requestDto);
return id;
}
//반환 타입을 void로 하는 개발자도 있음. 취향차이
}
▶ ProductRestController 개선하기
→ ProductRestController - 관심 상품 등록하기
@RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
@RestController // JSON으로 데이터를 주고받음을 선언합니다.
public class ProductRestController {
private final ProductRepository productRepository;
private final ProductService productService;
// 등록된 전체 상품 목록 조회
@GetMapping("/api/products")
public List<Product> getProducts() {
return productRepository.findAll();
}
//관심 상품 등록하기
@PostMapping("api/products")
public Product createProduct(@RequestBody ProductRequestDto requestDto){
Product product = new Product(requestDto);
return productRepository.save(product);
}
}
→ @RequestBody : JSON을 통해 전달되는 정보가 안에 쏙 알아서 잘 삽입되도록
[키워드로 상품 검색하기] - NaverShopsearch 발전시키기
▶ 요구 조건 살펴보기
→ 이전에 만들어 둔 NaverShopSearch 클래스를, 웹 서비스에 이용할 수 있도록 발전시켜보자
1. 검색어를 요구에 따라 바꿀 수 있어야한다.
2. 검색 결과를 문자열에서 DTO로 바꿔야한다.
→ 이전에 만들어 둔 NaverShopSearch에서 검색어를 "아이맥"으로 바꿔보자
public class NaverShopSearch {
public String search(String query) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "pxzfIwGsw2RVsbHyMBKG");
headers.add("X-Naver-Client-Secret", "_2o6in32bv");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
HttpStatus httpStatus = responseEntity.getStatusCode();
int status = httpStatus.value();
String response = responseEntity.getBody();
System.out.println("Response status: " + status);
System.out.println(response);
return response;
}
public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
naverShopSearch.search("아이맥");
}
}
▶ 요구 조건 살펴보기
→ 검색 결과를 문자열에서 DTO로 바꾸기
▶ org.json 패키지 설치하기
→ JSON을 자바에서 다루기 위해 JSONObject, JSONArray 클래스가 필요하다. (필요한 라이브러리!)
우리가 만드는 대신, 다른 사람이 만든 훌륭한 라이브러리를 바로 가져와서 사용할 예정!
이번에는 "다른 사람이 만든 라이브러리 가져오는 방법(임포트)"에 대해서 공부해볼 것이다.
1. 구글에 maven central 검색 후 첫 번째 결과 클릭
2. 검색창에 json 입력 후 엔터
3. JSON In Java 클릭
4. 숫자 가장 높은 버전 클릭
5. Gradle 탭 클릭
6. 내용 복사하여 build.gradle > dependencies 안에 붙여넣기
7. dependencies 옆의 Run 버튼 클릭
8. 임포트 완료!
▶ ItemDto 생성하기
→ ItemDto.java : 데이터를 물고 나르는 클래스
@Getter
public class ItemDto {
private String title;
private String link;
private String image;
private int lprice;
public ItemDto(JSONObject itemJson) { //JSONObject에서 String, int 데이터 뽑기
this.title = itemJson.getString("title");
this.link = itemJson.getString("link");
this.image = itemJson.getString("image");
this.lprice = itemJson.getInt("lprice");
}
}
▶ fromJSONtoItems 메소드 만들기
→ NaverShopSearch.java 파일에 추가/수정
public class NaverShopSearch {
public String search(String query) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "pxzfIwGsw2RVsbHyMBKG");
headers.add("X-Naver-Client-Secret", "_2o6in32bv");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
HttpStatus httpStatus = responseEntity.getStatusCode();
int status = httpStatus.value();
String response = responseEntity.getBody();
System.out.println("Response status: " + status);
System.out.println(response);
return response;
}
public List<ItemDto> fromJSONtoItems(String result){
JSONObject rjson = new JSONObject(result); //문자열 정보를 JSONObject로 바꾸기
JSONArray items = rjson.getJSONArray("items"); //JSONObject에서 items 배열 꺼내기
List<ItemDto> itemDtoList = new ArrayList<>();
for (int i=0; i<items.length(); i++) { //JSONArray로 for문 돌기
JSONObject itemJson = (JSONObject) items.get(i);
ItemDto itemDto = new ItemDto(itemJson);
itemDtoList.add(itemDto);
}
return itemDtoList;
}
[키워드로 상품 검색하기] - 네이버 API와 서비스 연결하기
▶ 요구 조건 살펴보기
1. 사용자가 검색어를 입력하면, 컨트롤러가 그것을 전달받는다.
2. 전달받은 검색어로 네이버 API에 요청하고, 그 결과를 사용자에게 응답한다.
▶ NaverShopSearch 컴포넌트 등록하기
→ 검색을 Controller에서 가져다 써야한다.
→ 스프링이 자동으로 필요한 클래스를 필요한 곳에 생성하려면, "아, 사용자가 요구하면 자동으로 생성할 클래스 목록이 이것이구나" 라고 확인할 수 있어야한다.
→ 그 목록에 등록하는 간단한 방법이 바로 컴포넌트이다.
→ 권한 획득, 스프링한테 권한을 주는 것! "야! 너 필요할 대 그때 그때 알아서 써!"
→ NaverShopSearch 컴포넌트 등록
@Component
public class NaverShopSearch {
public String search(String query) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "pxzfIwGsw2RVsbHyMBKG");
headers.add("X-Naver-Client-Secret", "_2o6in32bv");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
HttpStatus httpStatus = responseEntity.getStatusCode();
int status = httpStatus.value();
String response = responseEntity.getBody();
System.out.println("Response status: " + status);
System.out.println(response);
return response;
}
public List<ItemDto> fromJSONtoItems(String result){
JSONObject rjson = new JSONObject(result);
JSONArray items = rjson.getJSONArray("items");
List<ItemDto> itemDtoList = new ArrayList<>();
for (int i=0; i<items.length(); i++) {
JSONObject itemJson = (JSONObject) items.get(i);
ItemDto itemDto = new ItemDto(itemJson);
itemDtoList.add(itemDto);
}
return itemDtoList;
}
▶ SearchRequestController 만들기
→ 나만의 셀렉샵 API
→ SearchRequestController 만들기
@RequiredArgsConstructor // final 로 선언된 클래스를 자동으로 생성합니다. + Component 등록이 안돼있으면 불가능한다.
@RestController // JSON으로 응답함을 선언합니다.
public class SearchRequestController {
private final NaverShopSearch naverShopSearch;
@GetMapping("/api/search")
public List<ItemDto> getItems(@RequestParam String query) { //파라미터 query: '='앞에 있는 변수명이랑 똑같아야함
String resultString = naverShopSearch.search(query);
return naverShopSearch.fromJSONtoItems(resultString);
}
}
→ 파라미터 query : '=' 앞에 있는 변수명이랑 똑같아야함!
[HTML, 이미지 파일 준비하기]
→ 파일분리는 HTML파일이 CSS와 Javascript 때문에 지나치게 길어지는 것을 방지하고 가독성을 높이기 위한 방법이다.
1. .css와 .js로 끝나는 파일을 만든다.
2. link와 script 태그로 각 파일을 불러온다.
그러면 index.html 파일에 모두 작성한 것과 동일하게 작동한다.
→ 필기 사진으로 대체
→ 아이콘 저장과정 생략
[상품 검색 기능 만들기]
▶ execSearch, addHTML 함수 만들기
→ 검색창 입력값 가져오기
let query = $('#query').val();
→ 검색창 입력값을 검사하고, 입력하지 않았을 경우 focus
if (query == '') {
alert('검색어를 입력해주세요');
$('#query').focus();
return;
}
→ GET /api/search?query=${query} 요청
$.ajax({
type: 'GET',
url: `/api/search?query=${query}`,
success: function (response) {
$('#search-result-box').empty();
}
})
→ for문 마다 itemDto를 꺼내서 HTML 만들고 검색결과 목록에 붙이기!
for (let i = 0; i < response.length; i++) {
let itemDto = response[i];
let tempHtml = addHTML(itemDto);
$('#search-result-box').append(tempHtml);
}
→ addHTML 완성하기
function addHTML(itemDto) {
/**
* class="search-itemDto" 인 녀석에서
* image, title, lprice, addProduct 활용하기
* 참고) onclick='addProduct(${JSON.stringify(itemDto)})'
*/
return `<div class="search-itemDto">
<div class="search-itemDto-left">
<img src="${itemDto.image}" alt="">
</div>
<div class="search-itemDto-center">
<div>${itemDto.title}</div>
<div class="price">
${numberWithCommas(itemDto.lprice)}
<span class="unit">원</span>
</div>
</div>
<div class="search-itemDto-right">
<img src="images/icon-save.png" alt="" onclick='addProduct(${JSON.stringify(itemDto)})'>
</div>
</div>`
}
→ onClick에서 JSON.stringify로 JSON을 문자열로 바꿔서 보내야함, 클릭과 동시에 관심상품으로 등록됨
→ execSearch, addHTML 완성
function execSearch() {
/**
* 검색어 input id: query
* 검색결과 목록: #search-result-box
* 검색결과 HTML 만드는 함수: addHTML
*/
// 1. 검색창의 입력값을 가져온다.
let query = $('#query').val();
// 2. 검색창 입력값을 검사하고, 입력하지 않았을 경우 focus.
if (query == '') {
alert('검색어를 입력해주세요');
$('#query').focus(); //집중시키도록(검색창 강조)
return;
}
// 3. GET /api/search?query=${query} 요청
$.ajax({
type: 'GET',
url: `/api/search?query=${query}`,
success: function (response) {
$('#search-result-box').empty();
// 4. for 문마다 itemDto를 꺼내서 HTML 만들고 검색결과 목록에 붙이기!
for (let i = 0; i < response.length; i++) {
let itemDto = response[i];
let tempHtml = addHTML(itemDto);
$('#search-result-box').append(tempHtml);
}
}
})
}
function addHTML(itemDto) {
/**
* class="search-itemDto" 인 녀석에서
* image, title, lprice, addProduct 활용하기
* 참고) onclick='addProduct(${JSON.stringify(itemDto)})'
*/
return `<div class="search-itemDto">
<div class="search-itemDto-left">
<img src="${itemDto.image}" alt="">
</div>
<div class="search-itemDto-center">
<div>${itemDto.title}</div>
<div class="price">
${numberWithCommas(itemDto.lprice)}
<span class="unit">원</span>
</div>
</div>
<div class="search-itemDto-right">
<img src="images/icon-save.png" alt="" onclick='addProduct(${JSON.stringify(itemDto)})'>
</div>
</div>`
}
[관심 상품 등록하기]
▶ addProduct 함수 만들기
→ 관심 상품 생성 요청
function addProduct(itemDto) {
/**
* modal 뜨게 하는 법: $('#container').addClass('active');
* data를 ajax로 전달할 때는 두 가지가 매우 중요
* 1. contentType: "application/json",
* 2. data: JSON.stringify(itemDto),
*/
// 1. POST /api/products 에 관심 상품 생성 요청
$.ajax({
type: "POST",
url: '/api/products',
contentType: "application/json",
data: JSON.stringify(itemDto),
success: function (response) {
}
})
}
→ modal 뜨게 하기 : 최저가 설정 모달 뜨도록, 뒷배경 흐리게 나오고 팝업창 마냥 나오는 것
$('#container').addClass('active');
→ targetId = response.id
targetId = response.id;
→ addProduct 함수 완성
function addProduct(itemDto) {
/**
* modal 뜨게 하는 법: $('#container').addClass('active');
* data를 ajax로 전달할 때는 두 가지가 매우 중요
* 1. contentType: "application/json",
* 2. data: JSON.stringify(itemDto),
*/
// 1. POST /api/products 에 관심 상품 생성 요청
$.ajax({
type: "POST",
url: '/api/products',
contentType: "application/json",
data: JSON.stringify(itemDto),
success: function (response) {
// 2. 응답 함수에서 modal을 뜨게 하고, targetId 를 reponse.id 로 설정 (숙제로 myprice 설정하기 위함)
$('#container').addClass('active'); //id가 container인 녀석을 찾아서 class에 'active'인 것을 추가해준다.
targetId = response.id; //전역변수로, targetId에는 제일 최근에 담긴 관심 상품의 아이디가 들어감
}
})
}
→ targetId에는 제일 최근에 담긴 관심 상품의 아이디가 들어감 + 파일 제일 위에 어디서든 사용할 수 있도록 전역으로 선언해둠
[관심 상품 보여주기]
▶ $(document).ready 함수의 의미
→ 페이지가 모두 로드된 직후 실행할 자바스크립트 코드를 넣는 곳이다.
→ 일단 접속하면 관심 상품을 보여주어야 하기 때문에 showProduct 함수를 호출하고 있다.
→ 페이지를 새로고침 할 때마다 혹은 접속할 때마다 {} 안의 모든 것들이 실행됨
▶showProduct, addProductItem 만들기
→ showProduct : GET 방식으로 요청을 해서 관심 목록을 불러오는 녀석
→ addProductItem : 주어진 정보를 바탕으로 HTML 태그를 만들어서 변환해주는 녀석
→ GET/api/products 요청
// 1. GET /api/products 요청
$.ajax({
type: 'GET',
url: '/api/products',
success: function (response) {
}
})
→ 관심상품 목록, 검색결과 목록 비우기
// 2. 관심상품 목록, 검색결과 목록 비우기
$('#product-container').empty();
$('#search-result-box').empty();
→ for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
// 3. for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
for (let i = 0; i < response.length; i++) {
let product = response[i];
let tempHtml = addProductItem(product);
$('#product-container').append(tempHtml);
}
→ link, image, title, lprice, myprice 변수 활용하기
// link, image, title, lprice, myprice 변수 활용하기
return `<div class="product-card" onclick="window.location.href='${product.link}'">
<div class="card-header">
<img src="${product.image}"
alt="">
</div>
<div class="card-body">
<div class="title">
${product.title}
</div>
<div class="lprice">
<span>${numberWithCommas(product.lprice)}</span>원
</div>
<div class="isgood ${product.lprice > product.myprice ? 'none' : ''}">
최저가
</div>
</div>
</div>`;
+ 필기 사진 추가
[스케줄러 만들기] -> 자동으로 가격 정보를 업데이트 해주도록
→ 매일 새벽 1시에 관심상품목록 제목으로 검색해서, 최저가 정보를 업데이트하는 스케줄러를 만들어보자.
→ src > main > java > com.sparta.week04 > utils에 Scheduler.java 파일을 생성
→ Scheduler.java
@RequiredArgsConstructor // final 멤버 변수를 자동으로 생성합니다.
@Component // 스프링이 필요 시 자동으로 생성하는 클래스 목록에 추가합니다. -> 권한 주기
public class Scheduler {
private final ProductRepository productRepository;
private final ProductService productService;
private final NaverShopSearch naverShopSearch;
// 초, 분, 시, 일, 월, 주 순서
@Scheduled(cron = "0 0 1 * * *") //예약어? 정해진 시간에 하도록, *: 뭐든지 상관없다는 의미
public void updatePrice() throws InterruptedException {
System.out.println("가격 업데이트 실행");
// 저장된 모든 관심상품을 조회합니다.
List<Product> productList = productRepository.findAll();
for (int i=0; i<productList.size(); i++) {
// 1초에 한 상품 씩 조회합니다 (Naver 제한)
TimeUnit.SECONDS.sleep(1); //초마다 1초 쉬어라! 너무 자주 요청하면 naver에서 차단
// i 번째 관심 상품을 꺼냅니다.
Product p = productList.get(i);
// i 번째 관심 상품