Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/#8] 알라딘 api 연결, 책 리스트 검색 기능 구현 #14

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
.env
application-private.properties


### STS ###
.apt_generated
Expand Down Expand Up @@ -36,3 +38,4 @@ out/

### VS Code ###
.vscode/

Empty file added Dockerfile
Empty file.
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'org.json:json:20230618'
// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
}
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@ services:
volumes:
- mysql_data:/var/lib/mysql


volumes:
mysql_data:

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bookmile.backend.domain.book.controller;

import com.bookmile.backend.domain.book.dto.BooklistSearchRequestDto;
import com.bookmile.backend.domain.book.dto.BooklistSearchResponseDto;
import com.bookmile.backend.domain.book.service.BookService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/books")
public class BookController {

private final BookService bookService;

public BookController(BookService bookService) {
this.bookService = bookService;
}

// 도서 검색 API
@GetMapping("/search")
public ResponseEntity<List<BooklistSearchResponseDto>> searchBooks(@RequestParam String query) throws Exception {
BooklistSearchRequestDto request = new BooklistSearchRequestDto(query);

// 서비스 호출 및 결과 반환
List<BooklistSearchResponseDto> books = bookService.searchBooks(request);
return ResponseEntity.ok(books);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bookmile.backend.domain.book.dto;

public record BooklistSearchRequestDto (
String query,
String queryType,
int maxResults
) {
// 사용자 입력에 따라 기본값 설정
public BooklistSearchRequestDto(String query) {
this(query, "Title", 10); // 제목 검색과 최대 10개의 결과
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bookmile.backend.domain.book.dto;

public record BooklistSearchResponseDto (
String title,
String link,
String author,
String publisher,
String cover,
String description
) {}
38 changes: 26 additions & 12 deletions src/main/java/com/bookmile/backend/domain/book/entity/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,55 @@

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Book extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "book_id")
private long id;

@OneToMany(mappedBy = "book")
private List<Group> group = new ArrayList<>();
//@OneToMany(mappedBy = "book")
//private List<Group> group = new ArrayList<>();

@OneToMany(mappedBy = "book")
private List<Review> review = new ArrayList<>();
//@OneToMany(mappedBy = "book")
//private List<Review> review = new ArrayList<>();

@Column(nullable = false)
private String bookName;
private String title;

@Column(nullable = false)
private Integer page;

@Column(nullable = false)
private String thumbNail;
private String author;

@Column(nullable = false)
private String publisher;

@Column(nullable = false)
private String description;
private String cover;

@Column(nullable = false)
private String link;

@Column(nullable = false)
private Double rating;
private String description;

@Column
private Integer itemPage = null; //상품 조회하면 업데이트

@Column
private Integer bestSellerRank = null; //상품 조회하면 업데이트

@Column(nullable = false)
private Boolean isDeleted = false;

public Book(String title, String author, String publisher, String cover, String link, String description) {
this.title = title;
this.author = author;
this.publisher = publisher;
this.cover = cover;
this.link = link;
this.description = description;
}

protected Book() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bookmile.backend.domain.book.repository;

import com.bookmile.backend.domain.book.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
boolean existsByTitleAndAuthor(String title, String author);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.bookmile.backend.domain.book.service;

import com.bookmile.backend.domain.book.dto.BooklistSearchRequestDto;
import com.bookmile.backend.domain.book.dto.BooklistSearchResponseDto;
import com.bookmile.backend.domain.book.entity.Book;
import com.bookmile.backend.domain.book.repository.BookRepository;
import com.bookmile.backend.global.utils.AladinClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import org.json.JSONObject;
import org.json.JSONArray;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

private final AladinClient aladinClient;
private final BookRepository bookRepository;
private final ObjectMapper objectMapper; // Jackson ObjectMapper

public BookService(AladinClient aladinClient, BookRepository bookRepository, ObjectMapper objectMapper) {
this.aladinClient = aladinClient;
this.bookRepository = bookRepository;
this.objectMapper = objectMapper;
}

public List<BooklistSearchResponseDto> searchBooks(BooklistSearchRequestDto request) throws Exception {
// 1. 알라딘 API 호출
String jsonResponse = aladinClient.searchBooks(request.query(), request.queryType(), request.maxResults());

// 2. JSON 파싱 및 DTO 변환
List<BooklistSearchResponseDto> bookSearchResponses = parseSearchResponse(jsonResponse);

// 3. 데이터베이스에 저장
bookSearchResponses.forEach(response -> {
Book book = new Book(
response.title(),
response.link(),
response.author(),
response.publisher(),
response.cover(),
response.description()
);
bookRepository.save(book);
});

return bookSearchResponses;
}

private List<BooklistSearchResponseDto> parseSearchResponse(String jsonResponse) throws Exception {
// JSON 응답에서 item 배열 추출
JSONObject jsonObject = new JSONObject(jsonResponse);
JSONArray items = jsonObject.getJSONArray("item");

// JSON 배열을 List<BooklistSearchResponseDto>로 매핑
return objectMapper.readValue(items.toString(), new TypeReference<>() {
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.bookmile.backend.global.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
@ConfigurationProperties(prefix = "aladin.api")
@Getter
@Setter
public class AladinApiConfig {
private String baseUrl;
private String ttbKey;

// RestTemplate Bean 설정
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.defaultHeader("Accept", "application/json") // JSON 응답을 요청
.build();
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/bookmile/backend/global/utils/AladinClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.bookmile.backend.global.utils;

import com.bookmile.backend.global.config.AladinApiConfig;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.client.RestTemplate;

import java.net.URLEncoder;

@Component
public class AladinClient {

private final RestTemplate restTemplate;
private final AladinApiConfig config;

public AladinClient(RestTemplate restTemplate, AladinApiConfig config) {
this.restTemplate = restTemplate;
this.config = config;
}

public String searchBooks(String query, String queryType, int maxResults) throws Exception {

String url = UriComponentsBuilder.fromHttpUrl(config.getBaseUrl() + "/ItemSearch.aspx")
.queryParam("ttbkey", config.getTtbKey())
.queryParam("Query", URLEncoder.encode(query, "UTF-8"))
.queryParam("QueryType", queryType)
.queryParam("MaxResults", maxResults)
.queryParam("start", "1")
.queryParam("SearchTarget", "Book")
.queryParam("output", "JS") // JSON 형식으로 요청
.queryParam("Version", "20131101")

.toUriString();
// 요청 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, "application/json");
HttpEntity<String> entity = new HttpEntity<>(headers);

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

System.out.println("Request URL: " + url); // 디버깅용
System.out.println("API Response: " + response.getBody()); // 응답 데이터 출력
return response.getBody();
}
}
Empty file.
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
spring.application.name=backend
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
Expand All @@ -10,3 +11,5 @@ spring.datasource.url=jdbc:mysql://localhost:3307/bookmile?serverTimezone=Asia/S
spring.datasource.username=bookmile
spring.datasource.password=bookmile

aladin.api.base-url=https://www.aladdin.co.kr/ttb/api
aladin.api.ttb-key=ttb82everywin0100001