diff --git a/.gitignore b/.gitignore index 55608a8..2e2a04b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ .env +application-private.properties + ### STS ### .apt_generated @@ -36,3 +38,4 @@ out/ ### VS Code ### .vscode/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle index a78b131..c1d34b0 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } diff --git a/docker-compose.yml b/docker-compose.yml index ee5ccd3..c7d8067 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,5 @@ services: volumes: - mysql_data:/var/lib/mysql - volumes: mysql_data: - diff --git a/src/main/java/com/bookmile/backend/domain/book/controller/BookController.java b/src/main/java/com/bookmile/backend/domain/book/controller/BookController.java new file mode 100644 index 0000000..7326452 --- /dev/null +++ b/src/main/java/com/bookmile/backend/domain/book/controller/BookController.java @@ -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> searchBooks(@RequestParam String query) throws Exception { + BooklistSearchRequestDto request = new BooklistSearchRequestDto(query); + + // 서비스 호출 및 결과 반환 + List books = bookService.searchBooks(request); + return ResponseEntity.ok(books); + } +} diff --git a/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchRequestDto.java b/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchRequestDto.java new file mode 100644 index 0000000..a1d8839 --- /dev/null +++ b/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchRequestDto.java @@ -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개의 결과 + } +} diff --git a/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchResponseDto.java b/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchResponseDto.java new file mode 100644 index 0000000..308da7b --- /dev/null +++ b/src/main/java/com/bookmile/backend/domain/book/dto/BooklistSearchResponseDto.java @@ -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 +) {} \ No newline at end of file diff --git a/src/main/java/com/bookmile/backend/domain/book/entity/Book.java b/src/main/java/com/bookmile/backend/domain/book/entity/Book.java index 69611ef..967d923 100644 --- a/src/main/java/com/bookmile/backend/domain/book/entity/Book.java +++ b/src/main/java/com/bookmile/backend/domain/book/entity/Book.java @@ -17,7 +17,6 @@ @Entity @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Book extends BaseEntity { @Id @@ -25,33 +24,48 @@ public class Book extends BaseEntity { @Column(name = "book_id") private long id; - @OneToMany(mappedBy = "book") - private List group = new ArrayList<>(); + //@OneToMany(mappedBy = "book") + //private List group = new ArrayList<>(); - @OneToMany(mappedBy = "book") - private List review = new ArrayList<>(); + //@OneToMany(mappedBy = "book") + //private List 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() { + } } diff --git a/src/main/java/com/bookmile/backend/domain/book/repository/BookRepository.java b/src/main/java/com/bookmile/backend/domain/book/repository/BookRepository.java new file mode 100644 index 0000000..ed0ee45 --- /dev/null +++ b/src/main/java/com/bookmile/backend/domain/book/repository/BookRepository.java @@ -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 { + boolean existsByTitleAndAuthor(String title, String author); +} diff --git a/src/main/java/com/bookmile/backend/domain/book/service/BookService.java b/src/main/java/com/bookmile/backend/domain/book/service/BookService.java new file mode 100644 index 0000000..d7b4985 --- /dev/null +++ b/src/main/java/com/bookmile/backend/domain/book/service/BookService.java @@ -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 searchBooks(BooklistSearchRequestDto request) throws Exception { + // 1. 알라딘 API 호출 + String jsonResponse = aladinClient.searchBooks(request.query(), request.queryType(), request.maxResults()); + + // 2. JSON 파싱 및 DTO 변환 + List 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 parseSearchResponse(String jsonResponse) throws Exception { + // JSON 응답에서 item 배열 추출 + JSONObject jsonObject = new JSONObject(jsonResponse); + JSONArray items = jsonObject.getJSONArray("item"); + + // JSON 배열을 List로 매핑 + return objectMapper.readValue(items.toString(), new TypeReference<>() { + }); + } +} diff --git a/src/main/java/com/bookmile/backend/global/config/AladinApiConfig.java b/src/main/java/com/bookmile/backend/global/config/AladinApiConfig.java new file mode 100644 index 0000000..a11b8fc --- /dev/null +++ b/src/main/java/com/bookmile/backend/global/config/AladinApiConfig.java @@ -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(); + } +} diff --git a/src/main/java/com/bookmile/backend/global/utils/AladinClient.java b/src/main/java/com/bookmile/backend/global/utils/AladinClient.java new file mode 100644 index 0000000..21267cf --- /dev/null +++ b/src/main/java/com/bookmile/backend/global/utils/AladinClient.java @@ -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 entity = new HttpEntity<>(headers); + + ResponseEntity 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(); + } +} \ No newline at end of file diff --git a/src/main/resources/application-private.properties b/src/main/resources/application-private.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a835c9e..51f4dca 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 @@ -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