카테고리 없음

향해 week3 - 나만의 select shop 만들기

daram 2022. 3. 21. 17:04

select shop 소개


크게 2개의 페이지로 나눠져 있는 Select shop입니다.

 

모아보기: 즐겨 찾기 기능입니다.

내가 검색한 상품을 저장해 놓은 즐겨찾기 기능입니다

내가 생각한 최저가를 등록할 수 있으며

매일 1시에 가격을 자동으로 update 하고 난 뒤 내가 입력해 놓은 최저가 보다 낮을 경우 "최저가" 마크를 달아주고 있습니다.

 

탐색하기: 검색 기능입니다.

Naver api를 이용하여 상품을 검색할 수 있으며 검색한 상품을 클릭하여 즐겨 찾기(모아 보기)를 할 수 있습니다.

 

 

 

 

 

 

 

 

 

요구 기능


1. 상품명에 따른 검색 (naver api 사용)

2. 관심 상품 등록 & 조회 

3. 관심 상품에 대한 최저가 등록하기

4. 매일 1시 가격 갱신

 

 

API 설계


 

1. 상품명에 따른 검색 (naver api 사용)


네이버 api에 대한 사용법은 naver api 개발 가이드에서 더욱 자세하게 확인할 수 있따.

https://developers.naver.com/docs/serviceapi/search/blog/blog.md#%EB%B8%94%EB%A1%9C%EA%B7%B8

 

블로그 - Search API

블로그 NAVER Developers - 검색 API 블로그 검색 개발가이드 검색 > 블로그 네이버 블로그 검색 결과를 출력해주는 REST API입니다. 비로그인 오픈 API이므로 GET으로 호출할 때 HTTP Header에 애플리케이션

developers.naver.com

 

우리는 네이버 쇼핑을 타겟으로 검색을 시도할 것이기 때문에 아래와 같이 쿼리를 날려보았습니다.

Advanced Rest Client 를 사용하여 api를 테스트 해 보습니다. 

Naver api 사용설명서에 나와 있듯이 X-Naver-Client-Id 와 X-Naver-Client-Secret을 입력해주고 query를 날려보았습니다.

( X-Naver-Client-Id 와 X-Naver-Client-Secret 값은 오픈 API 이용 신청 을 진행하면 받을 수 있습니다. 본인의 id와 secret 값을 넣어주시면 됩니다.)

 

 

query를 정확하게 날리면 아래와 같은 응답을 받을 수 있습니다.

 

여기서 title, link, image, lprice 값만 받아서 view로 표시해주도록 해보겠습니다.

 

Advanced Rest Clinet 에서 code-snippets -> java -> spring 에 들어가면 sprint으로 query를 날리기 위한 command를 확인할 수 있습니다.

복사한 command를 가지고 NaverShopSearch라는 클래스를 만들어 주었습니다.

public class NaverShopSearch {

    public static void main(String[] args) {
        RestTemplate rest = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Naver-Client-Id", "");
        headers.add("X-Naver-Client-Secret", "");
        String body = "";

        HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
        ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=nike", 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);
    }
}

response 출력 결과를 살펴보고 어떤 것을 사용하면 될 지 생각해 보면 될 것 같습니다.

우리는 response 결과값에서 items 부분만을 들고와서 사용하면 될 것 같습니다.

값을 가져오기 편하게 JSON 형태로 변환을 시켜줘야 할 것 같습니다.

아래와 같이 build.gradle 파일에 dependency를 추가해 줍시다.

implementation group: 'org.json', name: 'json', version: '20160810'

자 이제 items를 우리의 입맛에 맞게 형태를 바꿔봅시다.

String response = responseEntity.getBody();

JSONObject jsonObject = new JSONObject(response);
JSONArray items = jsonObject.getJSONArray("items");

for (int i = 0; i < items.length(); i++) {
    JSONObject item = items.getJSONObject(i);
    int lprice = item.getInt("lprice");
    String link = item.getString("link");
    String title = item.getString("title");
    String image = item.getString("image");
    System.out.printf("%s\n%s\n%s\n%s",title, lprice, link, image);
    break;
}

출력 결과물은 아래와 같습니다.

딱 좋습니다.

우선은 NaverShopSearch 클래스는 이정도로 구현해놓고 넘어가도록 하겠습니다.

 

이번 프로젝트에서 사용할 index.html 입니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta property="og:title" content="재영이의 셀렉샵">
  <meta property="og:description" content="관심상품을 선택하고, 최저가 알림을 확인해보세요!">
  <meta property="og:image" content="images/og_selectshop.png">
  <link rel="stylesheet" href="style.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="basic.js"></script>
  <title>나만의 셀렉샵</title>
</head>
<body>
<div class="header">
  Select Shop
</div>
<div class="nav">
  <div class="nav-see active">
    모아보기
  </div>
  <div class="nav-search">
    탐색하기
  </div>
</div>
<div id="see-area">
  <div id="product-container">
    <div class="product-card" onclick="window.location.href='https://spartacodingclub.kr'">
      <div class="card-header">
        <img src="https://shopping-phinf.pstatic.net/main_2085830/20858302247.20200602150427.jpg?type=f300"
             alt="">
      </div>
      <div class="card-body">
        <div class="title">
          Apple 아이폰 11 128GB [자급제]
        </div>
        <div class="lprice">
          <span>919,990</span>원
        </div>
        <div class="isgood">
          최저가
        </div>
      </div>
    </div>
  </div>
</div>
<div id="search-area">
  <div>
    <input type="text" id="query">
    <!--    <img src="images/icon-search.png" alt="">-->
  </div>
  <div id="search-result-box">
    <div class="search-itemDto">
      <div class="search-itemDto-left">
        <img src="https://shopping-phinf.pstatic.net/main_2399616/23996167522.20200922132620.jpg?type=f300" alt="">
      </div>
      <div class="search-itemDto-center">
        <div>Apple 아이맥 27형 2020년형 (MXWT2KH/A)</div>
        <div class="price">
          2,289,780
          <span class="unit">원</span>
        </div>
      </div>
      <div class="search-itemDto-right">
        <img src="images/icon-save.png" alt="" onclick='addProduct()'>
      </div>
    </div>
  </div>
  <div id="container" class="popup-container">
    <div class="popup">
      <button id="close" class="close">
        X
      </button>
      <h1>⏰최저가 설정하기</h1>
      <p>최저가를 설정해두면 선택하신 상품의 최저가가 떴을 때<br/> 표시해드려요!</p>
      <div>
        <input type="text" id="myprice" placeholder="200,000">원
      </div>
      <button class="cta" onclick="setMyprice()">설정하기</button>
    </div>
  </div>
</div>
</body>
</html>

이번 프로젝트에서 사용할  style.css 입니다.

body {
    margin: 0px;
}

#search-result-box {
    margin-top: 15px;
}

.search-itemDto {
    width: 530px;
    display: flex;
    flex-direction: row;
    align-content: center;
    justify-content: space-around;
}

.search-itemDto-left img {
    width: 159px;
    height: 159px;
}

.search-itemDto-center {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-evenly;
}

.search-itemDto-center div {
    width: 280px;
    height: 23px;
    font-size: 18px;
    font-weight: normal;
    font-stretch: normal;
    font-style: normal;
    line-height: 1.3;
    letter-spacing: -0.9px;
    text-align: left;
    color: #343a40;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.search-itemDto-center div.price {
    height: 27px;
    font-size: 27px;
    font-weight: 600;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -0.54px;
    text-align: left;
    color: #E8344E;
}

.search-itemDto-center span.unit {
    width: 17px;
    height: 18px;
    font-size: 18px;
    font-weight: 500;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -0.9px;
    text-align: center;
    color: #000000;
}

.search-itemDto-right {
    display: inline-block;
    height: 100%;
    vertical-align: middle
}

.search-itemDto-right img {
    height: 25px;
    width: 25px;
    vertical-align: middle;
    margin-top: 60px;
    cursor: pointer;
}

input#query {
    padding: 15px;
    width: 526px;
    border-radius: 2px;
    background-color: #e9ecef;
    border: none;

    background-image: url('images/icon-search.png');
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 20px 20px;
}

input#query::placeholder {
    padding: 15px;
}

button {
    color: white;
    border-radius: 4px;
    border-radius: none;
}

.popup-container {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.5);
    align-items: center;
    justify-content: center;
}

.popup-container.active {
    display: flex;
}

.popup {
    padding: 20px;
    box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.3);
    position: relative;
    width: 370px;
    height: 190px;
    border-radius: 11px;
    background-color: #ffffff;
}

.popup h1 {
    margin: 0px;
    font-size: 22px;
    font-weight: 500;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -1.1px;
    color: #000000;
}

.popup input {
    width: 330px;
    height: 39px;
    border-radius: 2px;
    border: solid 1.1px #dee2e6;
    margin-right: 9px;
    margin-bottom: 10px;
    padding-left: 10px;
}

.popup button.close {
    position: absolute;
    top: 15px;
    right: 15px;
    color: #adb5bd;
    background-color: #fff;
    font-size: 19px;
    border: none;
}

.popup button.cta {
    width: 369.1px;
    height: 43.9px;
    border-radius: 2px;
    background-color: #15aabf;
    border: none;
}

#search-area, #see-area {
    width: 530px;
    margin: auto;
}

.nav {
    width: 530px;
    margin: 30px auto;
    display: flex;
    align-items: center;
    justify-content: space-around;
}

.nav div {
    cursor: pointer;
}

.nav div.active {
    font-weight: 700;
}

.header {
    background-color: #15aabf;
    color: white;
    text-align: center;
    padding: 50px;
    font-size: 45px;
    font-weight: bold;
}

#product-container {
    grid-template-columns: 100px 50px 100px;
    grid-template-rows: 80px auto 80px;
    column-gap: 10px;
    row-gap: 15px;
}

.product-card {
    width: 300px;
    margin: auto;
    cursor: pointer;
}

.product-card .card-header {
    width: 300px;
}

.product-card .card-header img {
    width: 300px;
}

.product-card .card-body {
    margin-top: 15px;
}

.product-card .card-body .title {
    font-size: 15px;
    font-weight: normal;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -0.75px;
    text-align: left;
    color: #343a40;
    margin-bottom: 10px;
}

.product-card .card-body .lprice {
    font-size: 15.8px;
    font-weight: normal;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -0.79px;
    color: #000000;
    margin-bottom: 10px;
}

.product-card .card-body .lprice span {
    font-size: 21.4px;
    font-weight: 600;
    font-stretch: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: -0.43px;
    text-align: left;
    color: #E8344E;
}

.product-card .card-body .isgood {
    margin-top: 10px;
    padding: 10px 20px;
    color: white;
    border-radius: 2.6px;
    background-color: #ff8787;
    width: 42px;
}

.none {
    display: none;
}

 

탐색하기 기능 구현

 

 

 

$(document).ready(function () {
  $('#see-area').show();
  $('#search-area').hide();
})

 

 

 

$(document).ready(function () {

  $('#see-area').show();
  $('#search-area').hide();

  $('.nav div.nav-see').on('click', function () {
      $('#see-area').show();
      $('#search-area').hide();
  })
  $('.nav div.nav-search').on('click', function () {
      $('#see-area').hide();
      $('#search-area').show();
  })
})

모아보기 화면
탐색하기 화면

// 검색 요청을 했을 경우
$('#query').on('keypress', function (e) {
    if (e.key == 'Enter') {
        execSearch();
    }
});

검색 요청을 했을 경우 실행
검색어가 빈 문자열인지 확인 (유효성 검사)
검색한 내용이 무엇인지 확인하고, 백엔드에 검색어를 넘긴다.
네이버 api 검색 결과를 반환 받고
반환 받은 내용을 토대로 뷰를 작성한다.

function execSearch() {
  // 검색한 내용이 무엇인지 확인
  let query = $('#query').val();

  // 빈 문자열인지 확인
  if (query == '') {
    alert("검색어를 입력하세요")
    $('#query').focus();
    return;
  }
  // 백엔드에 검색어를 넘긴다.
  $.ajax({
      type: 'GET',
      url: `/api/search?query=${query}`,
      success: function (response) {
      }
  })
}

 

자 이제 domain을 생성할 차례

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class TimeStamped {
    @CreatedDate
    private LocalDateTime createdAt;
    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

 

@EnableJpaAuditing
@SpringBootApplication
public class TimelineApplication {

   public static void main(String[] args) {
      SpringApplication.run(TimelineApplication.class, args);
   }

}

 

@Getter
public class ProductDto {
    private String title;

    private String image;

    private int lprice;

    private String link;
}

 

@Entity
public class Product extends TimeStamped {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String image;

    @Column(nullable = false)
    private int lprice;

    @Column(nullable = false)
    private String link;

    public Product(ProductDto productDto) {
        this.title = productDto.getTitle();
        this.image = productDto.getImage();
        this.lprice = productDto.getLprice();
        this.link = productDto.getLink();
    }
}

 

@RestController
public class ProductController {

    @GetMapping("/api/search")
    public void search(@RequestParam String query) {
        
    }
}

 

흠 main으로 만들어놨네?

사용하기 편하도록 변경해볼까?

public class NaverShopSearch {

    public static void main(String[] args) {
        RestTemplate rest = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Naver-Client-Id", "id 입력하세요");
        headers.add("X-Naver-Client-Secret", "password 입력하세요");
        String body = "";

        HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
        ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=nike", HttpMethod.GET, requestEntity, String.class);
        HttpStatus httpStatus = responseEntity.getStatusCode();
        int status = httpStatus.value();
        String response = responseEntity.getBody();

        JSONObject jsonObject = new JSONObject(response);
        JSONArray items = jsonObject.getJSONArray("items");

        for (int i = 0; i < items.length(); i++) {
            JSONObject item = items.getJSONObject(i);
            int lprice = item.getInt("lprice");
            String link = item.getString("link");
            String title = item.getString("title");
            String image = item.getString("image");
            System.out.printf("%s\n%s\n%s\n%s",title, lprice, link, image);
            break;
        }

    }
}

 

 

@Component
public class NaverShopSearch {

    public JSONObject search(String query) {
        RestTemplate rest = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Naver-Client-Id", "id를 입력하세요");
        headers.add("X-Naver-Client-Secret", "비밀번호를 입력하세요");
        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();

        JSONObject jsonObject = new JSONObject(response);
        return jsonObject;
    }

    public List<ProductDto> JsonObjectToProductDtoList(JSONObject jsonObject) {
        JSONArray items = jsonObject.getJSONArray("items");

        List<ProductDto> productDtoList = new ArrayList<>();
        for (int i = 0; i < items.length(); i++) {
            JSONObject item = items.getJSONObject(i);
            ProductDto productDto = new ProductDto(item);
            productDtoList.add(productDto);
        }
        return productDtoList;
    }
}

 

@RestController
@RequiredArgsConstructor
public class ProductController {

    private final NaverShopSearch naverShopSearch;

    @GetMapping("/api/search")
    public List<ProductDto> search(@RequestParam String query) {
        JSONObject jsonObject = naverShopSearch.search(query);
        List<ProductDto> productDtoList = naverShopSearch.JsonObjectToProductDtoList(jsonObject);
        return productDtoList;
    }
}

 

 

 

function execSearch() {
  // 검색한 내용이 무엇인지 확인
  let query = $('#query').val();

  // 빈 문자열인지 확인
  if (query == '') {
    alert("검색어를 입력하세요")
    $('#query').focus();
    return;
  }
  // 백엔드에 검색어를 넘긴다.

  $.ajax({
      type: 'GET',
      url: `/api/search?query=${query}`,
      success: function (response) {
        // 검색되어 있던 기록 없애고
        $('#search-result-box').empty();
        for (let i = 0; i < response.length; i++) {
          // 새로운 검색 결과 카드를 하나씩 생성
          let tempHtml = makeSearchHtml(response[i]);
          $('#search-result-box').append(tempHtml);
        }
      }
  })
}

function makeSearchHtml(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">
         ${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>`
}

 

 

검색 기능을 구현 완료하였습니다.

 

 

관심 상품 등록 & 조회 

빨간색 칸을 누르면 모아보기 칸으로 이동해야겠네요

 

 

<div class="search-itemDto-right">
  <img src="images/icon-save.png" alt="" onclick='addProduct()'>
</div>

itemDto를 백엔드로 넘긴다.
백엔드에서는 itemDto 정보를 db에 저장한다.

function addProduct(itemDto) {
//    console.log(itemDto);
//    console.log(JSON.stringify(itemDto))
    $.ajax({
        type: "POST",
        url: '/api/products',
        contentType: "application/json",
        data: JSON.stringify(itemDto),
        success: function (response) {

        }
    })
}

h2 database

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb

spring.jpa.show-sql=true

 

@Getter
public class ProductMypriceRequestDto {
    private int myprice;
}
@RestController
@RequiredArgsConstructor
public class ProductController {

    private final NaverShopSearch naverShopSearch;
    private final ProductRepository productRepository;

    @GetMapping("/api/search")
    public List<ProductDto> search(@RequestParam String query) {
        JSONObject jsonObject = naverShopSearch.search(query);
        List<ProductDto> productDtoList = naverShopSearch.JsonObjectToProductDtoList(jsonObject);
        return productDtoList;
    }

    @PostMapping("/api/products")
    public ProductDto save(@RequestBody ProductDto productDto) {
        Product product = new Product(productDto);
        productRepository.save(product);
        return productDto;
    }
}

noargsconstructor 추가

requestbody를 사용하려면 기본 생성자와 getter가 필요하다

@NoArgsConstructor
@Getter
public class ProductDto {
    private String title;

    private String image;

    private int lprice;

    private String link;

    public ProductDto(JSONObject item) {
        this.lprice = item.getInt("lprice");
        this.link = item.getString("link");
        this.title = item.getString("title");
        this.image = item.getString("image");
    }
}

 

 

$.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');
        targetId = response.id;
    }
})

 

 

modal

<div id="container" class="popup-container">
  <div class="popup">
    <button id="close" class="close">
      X
    </button>
    <h1>⏰최저가 설정하기</h1>
    <p>최저가를 설정해두면 선택하신 상품의 최저가가 떴을 때<br/> 표시해드려요!</p>
    <div>
      <input type="text" id="myprice" placeholder="200,000">원
    </div>
    <button class="cta" onclick="setMyprice()">설정하기</button>
  </div>
</div>

 

 

function setMyprice() {
    /**
     * 숙제! myprice 값 설정하기.
     * 1. id가 myprice 인 input 태그에서 값을 가져온다.
     * 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
     * 3. PUT /api/product/${targetId} 에 data를 전달한다.
     *    주의) contentType: "application/json",
     *         data: JSON.stringify({myprice: myprice}),
     *         빠뜨리지 말 것!
     * 4. 모달을 종료한다. $('#container').removeClass('active');
     * 5, 성공적으로 등록되었음을 알리는 alert를 띄운다.
     * 6. 창을 새로고침한다. window.location.reload();
     */
    // 1. id가 myprice 인 input 태그에서 값을 가져온다.
    let myprice = $('#myprice').val();
    // 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
    if (myprice == '') {
        alert('올바른 가격을 입력해주세요');
        return;
    }
    // 3. PUT /api/product/${targetId} 에 data를 전달한다.
    $.ajax({
        type: "PUT",
        url: `/api/products/${targetId}`,
        contentType: "application/json",
        data: JSON.stringify({myprice: myprice}),
        success: function (response) {
            // 4. 모달을 종료한다. $('#container').removeClass('active');
            $('#container').removeClass('active');
            // 5. 성공적으로 등록되었음을 알리는 alert를 띄운다.
            alert('성공적으로 등록되었습니다.');
            // 6. 창을 새로고침한다. window.location.reload();
            window.location.reload();
        }
    })
}

 

getter 를 달았다.

@Entity
@Getter
public class Product extends TimeStamped {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String image;

    @Column(nullable = false)
    private int lprice;

    @Column(nullable = false)
    private String link;

    private int myPrice;

    public Product(ProductDto productDto) {
        this.title = productDto.getTitle();
        this.image = productDto.getImage();
        this.lprice = productDto.getLprice();
        this.link = productDto.getLink();
        this.myPrice = 0;
    }

    public void updatePrice(int myPrice) {
        this.myPrice = myPrice;
    }
}
@RestController
@RequiredArgsConstructor
public class ProductController {

    private final NaverShopSearch naverShopSearch;
    private final ProductRepository productRepository;
    private final ProductService productService;

    @PutMapping("/api/products/{targetId}")
    public void update(@RequestBody ProductMypriceRequestDto requestDto, @PathVariable Long targetId) {
        productService.updatePrice(targetId, requestDto);

    }
}

 

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

    public void updatePrice(Long targetId, ProductMypriceRequestDto requestDto) {
        Product product = productRepository.findById(targetId).orElseThrow(
            () -> new IllegalArgumentException("상품이 존재하지 않습니다.")
        );

        product.updatePrice(requestDto.getMyprice());
    }
}

모달 끄기

$('#close').on('click', function () {
    $('#container').removeClass('active');
})

 

$(document).ready(function () {

  $('#see-area').show();
  $('#search-area').hide();

  // 모아보기 눌렀을 경우
  $('.nav div.nav-see').on('click', function () {
      $('#see-area').show();
      $('#search-area').hide();
  })

  // 탐색하기 눌렀을 경우
  $('.nav div.nav-search').on('click', function () {
      $('#see-area').hide();
      $('#search-area').show();
  })

  // 검색 요청을 했을 경우
  $('#query').on('keypress', function (e) {
      if (e.key == 'Enter') {
          execSearch();
      }
  });

  // 모달 x 버튼 클릭했을 때
  $('#close').on('click', function () {
      $('#container').removeClass('active');
  })

  showProduct();
})

 

function showProduct() {
/**
     * 관심상품 목록: #product-container
     * 검색결과 목록: #search-result-box
     * 관심상품 HTML 만드는 함수: addProductItem
     */

    // 1. GET /api/products 요청
    $.ajax({
      type: 'GET',
      url: '/api/products',
      success: function (response) {
          // 2. 관심상품 목록, 검색결과 목록 비우기
          $('#product-container').empty();
          $('#search-result-box').empty();
          // 3. for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
          for (let i = 0; i < response.length; i++) {
              let product = response[i];
              let tempHtml = addProductItem(product);
              $('#product-container').append(tempHtml);
          }
      }
  })
}
@RestController
@RequiredArgsConstructor
public class ProductController {

    private final NaverShopSearch naverShopSearch;
    private final ProductRepository productRepository;
    private final ProductService productService;

    @GetMapping("/api/products")
    public List<Product> show() {
        return productRepository.findAll();
    }
}
function showProduct() {
/**
     * 관심상품 목록: #product-container
     * 검색결과 목록: #search-result-box
     * 관심상품 HTML 만드는 함수: addProductItem
     */

    // 1. GET /api/products 요청
    $.ajax({
      type: 'GET',
      url: '/api/products',
      success: function (response) {
          // 2. 관심상품 목록, 검색결과 목록 비우기
          $('#product-container').empty();
          $('#search-result-box').empty();
          // 3. for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
          for (let i = 0; i < response.length; i++) {
              let product = response[i];
              let tempHtml = addProductItem(product);
              $('#product-container').append(tempHtml);
          }
      }
  })
}

 

function addProductItem(product) {
    // 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>`;
}