개발자가 말대꾸?
봄 백엔드 개발일기
개발자가 말대꾸?
전체 방문자
오늘
어제
  • 분류 전체보기 (42)
    • 알고리즘 공부 (13)
    • 디자인 패턴 공부 (1)
    • Spring (15)
      • Spring Boot (12)
      • Spring Data (1)
      • Spring Security (1)
    • Java (2)
    • MySQL (5)
    • EDITOR (3)
      • Intellij (3)
      • vscode (0)
    • 기타 (3)
      • 에러 (3)
      • 감상문 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • intelliJ 단축키
  • 프로그래머스 2단계
  • Java
  • Python
  • mysql
  • 프로그래머스
  • BasicAuthenticationFilter
  • RabbitMQ Kafka 차이
  • rest-api
  • Jpa 다중 제약조건 설정
  • jsp
  • MSA 아키텍처에서 Config Server의 변경 사항을 MSA에게 전달하는 방법
  • spring
  • 인텔리제이 사용법
  • JPA 여러 컬럼 Unique
  • JPA Unique 제약조건
  • BasicAuthorization
  • 코드 템플릿
  • 권한 프로그래밍
  • spring boot
  • JPA 여러 컬럼 유니크
  • UserDetails 도메인
  • 라이브 템플릿
  • SpringBoot
  • IntelliJ
  • SpringSecurity 프로젝트
  • intellij live templates
  • GrantedAuthority
  • JPA
  • 인텔리제이 좋은점

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
개발자가 말대꾸?

봄 백엔드 개발일기

Spring/Spring Boot

naver search api 사용해서 rest-api 구현하기

2022. 2. 11. 05:36

JPA가 없는 IN-MEMORY DB구현

MemoryDbRepositoryInterface

package com.example.restaurant.db;

import java.util.List;
import java.util.Optional;

public interface MemoryDbRepositoryIfs<T> {

    Optional<T> findById(int index);
    T save(T entity);
    void deleteById(int index);
    List<T> findAll();

}

MemoryDbRepositoryAbstract

package com.example.restaurant.db;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

abstract public class MemoryDbRepositoryAbstract<T extends MemoryDbEntity> implements MemoryDbRepositoryIfs<T> {

    private final List<T> db = new ArrayList<>();

    private int index = 0;

    @Override
    public Optional<T> findById(int index) {
        return db.stream()
                .filter(it -> it.getIndex() == index)
                .findFirst();
    }

    @Override
    public T save(T entity) {
        var optionalEntity = db.stream()
                .filter(it -> it.getIndex() == entity.getIndex())
                .findFirst();

        if(optionalEntity.isEmpty()){
            index ++;
            entity.setIndex(index);
            db.add(entity);
            return entity;
        }else{
            var preIndex = optionalEntity
                    .get()
                    .getIndex();

            entity.setIndex(preIndex);

            deleteById(preIndex);
            db.add(entity);
            return entity;
        }
    }

    @Override
    public void deleteById(int index) {
        var optionalEntity = db.stream()
                .filter(it -> it.getIndex() == index)
                .findFirst();

        if(optionalEntity.isPresent()){
            db.remove(optionalEntity.get());
        }
    }

    @Override
    public List<T> findAll() {
        return db;
    }
}

MemoryDbEntity

package com.example.restaurant.db;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class MemoryDbEntity {
    protected Integer index;
}

Naver api - Client

searchImageRequest

package com.example.restaurant.naver.dto;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchImageReq {

    private String query = "";

    private int display = 1;

    private int start = 1;

    private String sort =  "sim";

    private String filter = "all";

    public MultiValueMap<String,String> toMultiValueMap(){
        var map = new LinkedMultiValueMap<String,String>();

        map.add("query",query);
        map.add("display", String.valueOf(display));
        map.add("start", String.valueOf(start));
        map.add("sort",sort);
        map.add("filter",filter);

        return map;
    }
}

searchImageResponse

package com.example.restaurant.naver.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchImageRes {

    private String lastBuildDate;

    private int total;

    private int start;

    private int display;

    private List<SearchImageItem> items;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SearchImageItem {
        private String title;
        
        private String link;

        private String thumbnail;

        private String sizeheight;

        private String sizewidth;
    }
}

searchLocalRequest

package com.example.restaurant.naver.dto;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchLocalReq {

    private String query = "";

    private int display = 1;

    private int start = 1;

    private String sort =  "comment";

    public MultiValueMap<String,String> toMultiValueMap(){
        var map = new LinkedMultiValueMap<String,String>();

        map.add("query",query);
        map.add("display", String.valueOf(display));
        map.add("start", String.valueOf(start));
        map.add("sort",sort);

        return map;
    }
}

searchLocalResponse

package com.example.restaurant.naver.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchLocalRes {

    private String lastBuildDate;

    private int total;

    private int start;

    private int display;

    private List<SearchLocalItem> items;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SearchLocalItem {


        private String title;

        private String link;

        private String description;

        private String category;

        private String telephone;

        private String address;

        private String roadAddress;

        private int mapx;

        private int mapy;
    }
}

Naver - Client

NaverClient

package com.example.restaurant.naver;


import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchImageRes;
import com.example.restaurant.naver.dto.SearchLocalReq;
import com.example.restaurant.naver.dto.SearchLocalRes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

@Component
public class NaverClient {

    @Value("${naver.client.id}")
    private String naverClientId;

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

    @Value("${naver.url.search.local}")
    private String naverLocalSearchUrl;

    @Value("${naver.url.search.image}")
    private String naverImageSearchUrl;


    // -- search local -- //

    public SearchLocalRes searchLocal(SearchLocalReq searchLocalReq){
        var uri = UriComponentsBuilder
                .fromUriString(naverLocalSearchUrl)
                .queryParams(searchLocalReq.toMultiValueMap())
                .build()
                .encode()
                .toUri();

        var headers = new HttpHeaders();
        headers.set("X-Naver-Client-Id",naverClientId);
        headers.set("X-Naver-Client-Secret",naverClientSecret);
        headers.setContentType(MediaType.APPLICATION_JSON);

        var httpEntity = new HttpEntity<>(headers);
        var responseType = new ParameterizedTypeReference<SearchLocalRes>(){};

        var responseEntity = new RestTemplate()
                .exchange(
                        uri,
                        HttpMethod.GET,
                        httpEntity,
                        responseType
                );

        return responseEntity.getBody();
    }

    // -- search Image  -- //

    public SearchImageRes searchImage(SearchImageReq searchImageReq){
        var uri = UriComponentsBuilder
                .fromUriString(naverImageSearchUrl)
                .queryParams(searchImageReq.toMultiValueMap())
                .build()
                .encode()
                .toUri();

        var headers = new HttpHeaders();
        headers.set("X-Naver-Client-Id",naverClientId);
        headers.set("X-Naver-Client-Secret",naverClientSecret);
        headers.setContentType(MediaType.APPLICATION_JSON);

        var httpEntity = new HttpEntity<>(headers);
        var responseType = new ParameterizedTypeReference<SearchImageRes>(){};

        var responseEntity = new RestTemplate()
                .exchange(
                        uri,
                        HttpMethod.GET,
                        httpEntity,
                        responseType
                );

        return responseEntity.getBody();
    }
}

WishList


- dto

package com.example.restaurant.wishlist.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class WishListDto{

    private Integer index;

    private String title;           // 음식명, 장소명
    private String category;        // 카테고리
    private String address;         // 주소
    private String roadAddress;     // 도로명 주소
    private String homePageLink;    // 홈페이지 주소
    private String imageLink;       // 음식, 가게 이미지 주소
    private boolean isVisit;        // 방문여부
    private int visitCount;         // 방문 횟수
    private LocalDateTime lastVisitDate; // 마지막 방문 일자.
}


- entity

package com.example.restaurant.wishlist.entity;

import com.example.restaurant.db.MemoryDbEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class WishListEntity extends MemoryDbEntity {

    private String title;           // 음식명, 장소명
    private String category;        // 카테고리
    private String address;         // 주소
    private String roadAddress;     // 도로명 주소
    private String homePageLink;    // 홈페이지 주소
    private String imageLink;       // 음식, 가게 이미지 주소
    private boolean isVisit;        // 방문여부
    private int visitCount;         // 방문 횟수
    private LocalDateTime lastVisitDate; // 마지막 방문 일자.

}


- repository

package com.example.restaurant.wishlist.repository;

import com.example.restaurant.db.MemoryDbRepositoryAbstract;
import com.example.restaurant.wishlist.entity.WishListEntity;
import org.springframework.stereotype.Repository;

@Repository
public class WishListRepository extends MemoryDbRepositoryAbstract<WishListEntity> {}

- service

package com.example.restaurant.wishlist.service;


import com.example.restaurant.naver.NaverClient;
import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchLocalReq;
import com.example.restaurant.wishlist.dto.WishListDto;
import com.example.restaurant.wishlist.entity.WishListEntity;
import com.example.restaurant.wishlist.repository.WishListRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor

public class WishListService {

    private final NaverClient naverClient;

    private final WishListRepository wishListRepository;

    public WishListDto search(String query) {

        // 지역검색
        var searchLocalReq = new SearchLocalReq();
        searchLocalReq.setQuery(query);

        var searchLocalRes = naverClient.searchLocal(searchLocalReq);

        if (searchLocalRes.getTotal() > 0) {
            var localItem = searchLocalRes
                    .getItems()
                    .stream()
                    .findFirst()
                    .get();

            var imageQuery = localItem
                    .getTitle()
                    .replaceAll("<[^>]*>", ""); // 정규표현식


            var searchImageReq = new SearchImageReq();
            searchImageReq.setQuery(imageQuery);

            // 이미지 검색
            var searchImageRes = naverClient.searchImage(searchImageReq);

            if (searchLocalRes.getTotal() > 0) {
                var rst = new WishListDto();
                var imageItem = searchImageRes
                        .getItems()
                        .stream()
                        .findFirst()
                        .get();

                rst.setTitle(localItem.getTitle());
                rst.setCategory(localItem.getCategory());
                rst.setAddress(localItem.getAddress());
                rst.setRoadAddress(localItem.getRoadAddress());
                rst.setHomePageLink(localItem.getLink());
                rst.setImageLink(imageItem.getLink());

                return rst;



            }
        }

        return new WishListDto();

    }


    public WishListDto add(WishListDto wishlistDto) {

        var entity = dtoToEntity(wishlistDto);
        var saveEntity = wishListRepository.save(entity);

        return entityToDto(saveEntity);
    }


    private WishListDto entityToDto(WishListEntity wishListEntity) {

        var dto = new WishListDto();

        dto.setIndex(wishListEntity.getIndex());
        dto.setTitle(wishListEntity.getTitle());
        dto.setCategory(wishListEntity.getCategory());
        dto.setAddress(wishListEntity.getAddress());
        dto.setRoadAddress(wishListEntity.getRoadAddress());
        dto.setHomePageLink(wishListEntity.getHomePageLink());
        dto.setImageLink(wishListEntity.getImageLink());
        dto.setVisit(wishListEntity.isVisit());
        dto.setVisitCount(wishListEntity.getVisitCount());
        dto.setLastVisitDate(wishListEntity.getLastVisitDate());

        return dto;
    }

    private WishListEntity dtoToEntity(WishListDto wishListDto) {
        var entity = new WishListEntity();

        entity.setIndex(wishListDto.getIndex());
        entity.setTitle(wishListDto.getTitle());
        entity.setCategory(wishListDto.getCategory());
        entity.setAddress(wishListDto.getAddress());
        entity.setRoadAddress(wishListDto.getRoadAddress());
        entity.setHomePageLink(wishListDto.getHomePageLink());
        entity.setImageLink(wishListDto.getImageLink());
        entity.setVisit(wishListDto.isVisit());
        entity.setVisitCount(wishListDto.getVisitCount());
        entity.setLastVisitDate(wishListDto.getLastVisitDate());

        return entity;
    }

    public List<WishListDto> findAll() {
        return wishListRepository.findAll()
                .stream()
                .map((it) ->
                        entityToDto(it))
                .collect(Collectors
                        .toList());
    }

    public void delete(int index) {
        wishListRepository.deleteById(index);

    }

    public void addVisit(int index){
        var wishItem = wishListRepository.findById(index);

        if(wishItem.isPresent()){
            var item = wishItem.get();

            item.setVisit(true);
            item.setVisitCount(item.getVisitCount()+1);
        }
    }
}

 

JUnit 단위 테스트

package com.example.restaurant.naver;


import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchLocalReq;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class NaverClientTest {

    @Autowired
    private NaverClient naverClient;

    @Test
    public void searchLocalTest(){

        var search = new SearchLocalReq();

        search.setQuery("갈비집");

        var rst = naverClient.searchLocal(search);
        System.out.println(rst);
        Assertions.assertNotNull(rst
                .getItems()
                .stream()
                .findFirst()
                .get()
                .getCategory());

    }

    @Test
    public void searchImageTest(){

        var search = new SearchImageReq();

        search.setQuery("갈비집");

        var rst = naverClient.searchImage(search);

        System.out.println(rst);

    }
}

단위 테스트에는 Slf4j를 사용할 수 없다는 사실을 처음 알았다..

통합테스트

package com.example.restaurant.wishlist.repository;

import com.example.restaurant.wishlist.entity.WishListEntity;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class WishListRepositoryTest {

    @Autowired
    private WishListRepository wishListRepository;

    private WishListEntity create(){
        var wishList = new WishListEntity();
        wishList.setTitle("title");
        wishList.setCategory("category");
        wishList.setAddress("address");
        wishList.setRoadAddress("readAddress");
        wishList.setHomePageLink("");
        wishList.setImageLink("");
        wishList.setVisitCount(0);
        wishList.setVisit(false);
        wishList.setLastVisitDate(null);
        return wishList;
    }


    @Test
    public void saveTest(){
        var wishListEntity = create();
        var expected = wishListRepository.save(wishListEntity);

        Assertions.assertNotNull(expected);
        Assertions.assertEquals(1, expected.getIndex());
    }

    @Test
    public void updateTest(){
        var wishListEntity = create();
        var expected = wishListRepository.save(wishListEntity);

        expected.setTitle("update test");
        var saveEntity = wishListRepository.save(expected);

        Assertions.assertEquals("update test",saveEntity.getTitle());
        Assertions.assertEquals(1,wishListRepository
                .findAll()
                .size());

    }


    @Test
    public void findByIdTest(){
        var wishListEntity = create();
        wishListRepository.save(wishListEntity);

        var expected = wishListRepository.findById(1);

        Assertions.assertEquals(true,expected.isPresent());
        Assertions.assertEquals(1, expected.get().getIndex());
    }
    @Test
    public void deleteTest(){
        var wishListEntity = create();
        wishListRepository.save(wishListEntity);

        wishListRepository.deleteById(1);

        int count = wishListRepository
                .findAll()
                .size();

        Assertions.assertEquals(0,count);
    }
    @Test
    public void listAllTest(){
        var wishListEntity1 = create();
        wishListRepository.save(wishListEntity1);

        var wishListEntity2 = create();
        wishListRepository.save(wishListEntity2);

        int count = wishListRepository
                .findAll()
                .size();

        Assertions.assertEquals(2,count);

    }
}

마지막으로 application.yml

'Spring > Spring Boot' 카테고리의 다른 글

[게시판 RESTful API] - 프로젝트 세팅 (1)  (0) 2022.08.20
[Spring Boot] - 유효성 검증을 하는 방법과 유효성 검증 실패 시 Exception핸들링을 하는 4가지 방법을 알아보자  (0) 2022.08.13
[Spring Boot, RIOT API] - Unirest로 RIOT API를 요청하고 ObjectMapper를 배워보자.  (0) 2022.08.10
[Spring Boot] Transactional 어노테이션에 대해 알아보자.  (0) 2022.04.30
[Spring Data Jpa] (JUNIT, DDL) 외래키 제약조건에 NOT NULL을 추가해보자.  (0) 2022.04.23
    'Spring/Spring Boot' 카테고리의 다른 글
    • [Spring Boot] - 유효성 검증을 하는 방법과 유효성 검증 실패 시 Exception핸들링을 하는 4가지 방법을 알아보자
    • [Spring Boot, RIOT API] - Unirest로 RIOT API를 요청하고 ObjectMapper를 배워보자.
    • [Spring Boot] Transactional 어노테이션에 대해 알아보자.
    • [Spring Data Jpa] (JUNIT, DDL) 외래키 제약조건에 NOT NULL을 추가해보자.
    개발자가 말대꾸?
    개발자가 말대꾸?
    - ing9990.com - 열정적인 ENTP - 주말 코딩, 퇴근 코딩 ing9990

    티스토리툴바