[Project] 네이버 도서 API 이용해 간단한 도서 검색 web 만들기 (with. Spring Boot)

gogori6565·2024년 8월 4일
0

스프링 부트 공부

목록 보기
21/21
post-thumbnail

Core 프로젝트에서 도서 API를 사용하기 전에 미리 사용하는 방법을 익혀보려고 간단한 도서 검색 web page를 만들어보기로 했다.

우선 API 사용을 위해 네이버 개발자 페이지에서 애플리케이션 등록을 해주자.

🧷 NAVER Developers - 애플리케이션 등록 [link]


🔹Postman 으로 API 요청해보기

등록 후, API 사용 Key를 받았으니 우선 Postman을 사용해서 데이터가 잘 받아와지는지 확인해보자.

🧷 NAVER 검색>도서 API 사용 매뉴얼 을 참고하여 진행해보자.

HTTP 메서드는 GET + 위에 나와있는 요청 URL을 넣고(필자는 JSON으로 반환받을 것이다),
참고 사항에 나와있는대로 Header 영역에 클라이언트 아이디와 클라이언트 시크릿을 추가한다.

그리고 Params 영역에 위에 나와있는 파라미터를 토대로 원하는 검색 결과를 설정한다. (일단 query(검색어) 만 사용해 검색해보자)

요청을 보내니 Body에 검색한 도서에 대한 정보가 성공적(200)으로 돌아오는 것을 확인할 수 있다.


🔹도서 검색 web 만들기

IDE : intelliJ
Framework : Spring Boot 3.3.2
Java version : JAVA 17

build.gradle > dependencies

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-mustache'

	//lombok add!
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

본격적으로 Spring Boot에서 API를 사용하는 방법을 실습해보자!
간단하게, 검색어 입력받고 그에 대한 책 목록을 받아와 출력하는 web을 만들어 볼 것이다.

주요하게 만들어야 할 건 딱 세가지 이다.

  1. 검색할 화면(search.mustache)과 검색 결과를 보여줄 화면(result.mustache)
  2. API 요청을 보낼 controller
  3. API 요청 데이터를 담을 VO

BookController

일단 controller의 대략적인 틀을 만들자. 검색어를 받고 출력하는 것 모두 Get 방식으로 진행했다. (처음에 Post와 Get을 나눴다가 굳이? 라는 생각이 들었따. 정말 굳이 였따 ㅎ)

@Controller
public class BookController {

    @GetMapping("/book/search")
    public String search() {
        return "search";
    }
    
    @GetMapping("/book/result")
    public String result() {
    	result "result";
    }

VO - 응답 데이터 담기

VO 작성 시, 위의 응답 데이터를 참고하여 작성해야 한다.
<빨간 박스>는 ResultVO.java 에, <파란 박스>는 BookVO.java 에 작성하자.

ResultVO.java

@ToString
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class ResultVO {
    private String lastBuildDate;
    private int total;
    private int start;
    private int display;
    private List<BookVO> items;
}

BookVO.java

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
public class BookVO {
    private String title;
    private String link;
    private String image;
    private String author;
    private String discount;
    private String publisher;
    private String isbn;
    private String description;
    private String pubdate;
}

.mustache - 검색 화면 & 결과 화면

mustache 사용 방법

  • File > Settings > Plugins > Handlebars/Mustache 설치
  • build.gradle > dependencies 에 implementation 'org.springframework.boot:spring-boot-starter-mustache' 추가

search.mustache

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>book search</title>
</head>
<body>
    <h1>도서 검색</h1>
    <form action="/book/result" method="get">
        <input type="text" placeholder="검색어를 입력하세요" name="bookname" required>
        <button type="submit">확인</button>
    </form>
</body>
</html>

result.mustache

<!DOCTYPE html>
<html lang="en">
<head>
    <title>book list</title>
</head>
<body>
<h2>검색한 도서 목록 : "{{text}}"에 대한 검색어</h2>
<table border="1">
    <tr bgcolor="pink">
        <td>isbn</td>
        <td>도서 이미지</td>
        <td>도서제목</td>
        <td>저자/출판사</td>
        <td>소개</td>
        <td>출판일</td>
    </tr>
    {{#books}}
        <tr>
            <td>{{isbn}}</td>
            <td><img src="{{image}}" alt="{{title}}" width="60%" height="60%"></td>
            <td><a href="{{link}}">{{title}}</a></td>
            <td>{{author}}/{{publisher}}</td>
            <td>{{description}}</td>
            <td>{{pubdate}}</td>
        </tr>
    {{/books}}
</table>
</body>
</html>

Controller - API 요청 코드

검색어 가져오기

public String result(@RequestParam("bookname") String text, Model model) {
    log.info("Received text: {}", text);
    model.addAttribute("text", text);
  • @RequestParam("bookname") : Http 요청 파라미터에서 "bookname"의 값을 가져와 text에 주입한다.

클라이언트 아이디/시크릿 값 입력

String clientId = "클라이언트 아이디 값";
String clientSecret = "클라이언트 시크릿 값";

Spring apiURI

URI uri = UriComponentsBuilder
		.fromUriString("https://openapi.naver.com")
        .path("/v1/search/book.json")
        .queryParam("query", text)
        .queryParam("display", 10)
        .queryParam("start", 1)
        .queryParam("sort", "sim")
        .encode()
        .build()
        .toUri();

Spring 요청 제공 클래스

RequestEntity<Void> req = RequestEntity.get(uri)
            .header("X-Naver-Client-Id", clientId)
            .header("X-Naver-Client-Secret", clientSecret)
            .build();

Spring 제공 RestTemplate

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> resp = restTemplate.exchange(req, String.class); //응답 본문 String 타입으로 변환

JSON 파싱 (JSON 문자열을 객체로 만듦, 문서화)

ObjectMapper om = new ObjectMapper();
ResultVO resultVO = null;

try{
    resultVO = om.readValue(resp.getBody(), ResultVO.class);
} catch (JsonMappingException e){
    e.printStackTrace();
} catch (JsonProcessingException e){
    e.printStackTrace();
}

result.mustache 에 값 넘겨주기

List<BookVO> books = resultVO.getItems();
model.addAttribute("books", books);

📌 전체 코드

@GetMapping("/book/result")
public String result(@RequestParam("bookname") String text, Model model) {
    log.info("Received text: {}", text);
    model.addAttribute("text", text);

    //네이버 검색 API 요청 - 클라이언트 아이디/시크릿 값 입력
    String clientId = "클라이언트 아이디 값";
    String clientSecret = "클라이언트 시크릿 값";

    //String apiURL
    URI uri = UriComponentsBuilder
            .fromUriString("https://openapi.naver.com")
            .path("/v1/search/book.json")
            .queryParam("query", text)
            .queryParam("display", 10)
            .queryParam("start", 1)
            .queryParam("sort", "sim")
            .encode()
            .build()
            .toUri();

    //Spring 요청 제공 클래스
    RequestEntity<Void> req = RequestEntity.get(uri)
            .header("X-Naver-Client-Id", clientId)
            .header("X-Naver-Client-Secret", clientSecret)
            .build();

    //Spring 제공 RestTemplate
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> resp = restTemplate.exchange(req, String.class); //응답 본문 String 타입으로 변환

    //JSON 파싱 (JSON 문자열을 객체로 만듦, 문서화)
    ObjectMapper om = new ObjectMapper();
    ResultVO resultVO = null;

    try{
        resultVO = om.readValue(resp.getBody(), ResultVO.class);
    } catch (JsonMappingException e){
        e.printStackTrace();
    } catch (JsonProcessingException e){
        e.printStackTrace();
    }

    //result.mustahce 에 값 넘겨주기
    List<BookVO> books = resultVO.getItems();
    model.addAttribute("books", books);

    return "result";
}

➕ 만약.. controller의 result() 메서드가 두 번 호출되는 문제가 발생한다면...

이거 때문에 몇 시간을 삽질했는지 모른다.

개발을 할 때 로그를 보니 result() 메서드가 두 번 호출돼서 API 요청이 두 번 되는 문제가 있었다.
처음에는 내가 Post 요청으로 redirect를 해서 문제가 발생한 건가 엄청난 삽질을 했는데...

알고보니 해결은 간단했다.

html 내에 link 태그 안에 불러오는 주소를 비워두든가 경로를 잘못 설정할 경우 이 문제가 발생한다는 것이다.

<td><img src="#" alt=""></td>
<td><a href="#">도서제목</a></td>

그러니 # 이나 공백으로 비워두지 말고 걍 없애버려라... 흑흑


개념 정리

VO

  • VO는 값을 위해 사용 (readOnly)
  • [DTO와의 차이점]
    - VO는 사용되는 값이 객체로 표현되고 값에 대한 변경이 없는 경우
    - DTO는 데이터 전송을 위한 객체로 비즈니스 로직을 담아서 활용

RestTemplate 과 RequestEntity

<RestTemplateRequestEntity 는 Spring Framework에서 제공하는 클래스로, 웹 서비스와의 HTTP 통신을 돕는 역할을 한다, 이 클래스들을 사용하면 웹 서비스를 호출하거나 응답을 처리하는 작업을 간결하게 할 수 있음>

  • RestTemplate
    - Spring에서 제공하는 HTTP 클라이언트 유틸리티 클래스. 사용하면 RESTful 웹 서비스를 쉽게 호출할 수 있다.
    - HTTP 클라이언트 유틸리티 클래스 : 프로그래밍에서 HTTP 방식으로 정보를 주고 받기 위해 사용하는 도구 혹은 기능을 모아 놓은 설계도(클래스)를 의미
  • RequestEntity
    - HTTP 요청을 표현하는 클래스. 요청 메서드, URL, 헤더 및 본문 포함 가능

👉 즉, RequestEntity 를 사용해 요청 정보를 정의하고, RestTemplate 의 exchange 메서드를 통해 실제 요청을 보낸다. 그 후에 서버로부터의 응답은 ResponseEntity 객체에 담게 된다.

ObjetMapper

  • ObjectMapper를 이용하면 JSON을 Java 객체로 변환할 수 있고, 반대로 Java 객체를 JSON 객체로 serialization(직렬화) 할 수 있다.

  • writeValue() : Java 객체를 JSON으로 serialization 하기 위해서는 ObjectMapper의 writeValue() 메서드를 이용

  • readValue() : JSON 파일을 Java 객체로 deserialization 하기 위해서는 ObjectMapper의 readValue() 메서드를 이용


이거 하면서 느낀점

  • build.gradle 에 사용할 의존성을 잘 쓰자.. 빼먹지 말고... 괜한 코드로 삽질하지 말고...
  • Chat GPT 를 너무 믿지 말자. 오류가 나면 구글에 쳐보자..

참고 사이트
https://this-circle-jeong.tistory.com/167 - 네이버 도서 API 활용

profile
p(´∇`)q

0개의 댓글