Skip to content

Commit

Permalink
Merge pull request #76 from lotteon2/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
wakkpu authored Jan 25, 2024
2 parents 9dc4800 + eb8f77f commit 157b966
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 52 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

// gson
implementation 'com.google.code.gson:gson:2.7'

// querydsl
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.dailyon.productservice.brand.dto.response;

import com.dailyon.productservice.brand.entity.Brand;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;

@Getter
@Builder
Expand All @@ -14,6 +11,11 @@ public class ReadBrandResponse {
private Long id;
private String name;

@Override
public String toString() {
return '{' + "'id':" + this.id + ", 'name': '" + this.name + "'}";
}

public static ReadBrandResponse fromEntity(Brand brand) {
return ReadBrandResponse.builder()
.id(brand.getId())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.dailyon.productservice.category.dto.response;

import com.dailyon.productservice.category.entity.Category;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;

@Getter
@Builder
Expand All @@ -14,6 +11,11 @@ public class ReadChildrenCategoryResponse {
private Long id;
private String name;

@Override
public String toString() {
return '{' + "'id':" + this.id + ", 'name': '" + this.name + "'}";
}

public static ReadChildrenCategoryResponse fromEntity(Category category) {
return ReadChildrenCategoryResponse.builder()
.id(category.getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,8 @@ public void deleteCategory(Long categoryId) {
categories.forEach(Category::softDelete);
productSizeRepository.deleteProductSizesByCategory(categories);
}

public List<Category> findAllChildCategories(Long categoryId) {
return categoryRepository.findAllChildCategories(categoryId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dailyon.productservice.common.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.dailyon.productservice.common.feign.client;

import com.dailyon.productservice.brand.dto.response.ReadBrandListResponse;
import com.dailyon.productservice.brand.dto.response.ReadBrandResponse;
import com.dailyon.productservice.brand.repository.BrandRepository;
import com.dailyon.productservice.category.dto.response.ReadChildrenCategoryListResponse;
import com.dailyon.productservice.category.dto.response.ReadChildrenCategoryResponse;
import com.dailyon.productservice.category.repository.CategoryRepository;
import com.dailyon.productservice.common.enums.Gender;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class OpenAIClient {

private final Environment environment;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
private final BrandRepository brandRepository;
private final CategoryRepository categoryRepository;

public String getSearchResults(String searchQuery) throws Exception {
List<ReadBrandResponse> brands = ReadBrandListResponse
.fromEntity(brandRepository.findAll())
.getBrandResponses();

String allBrands = brands.stream()
.map(ReadBrandResponse::toString)
.collect(Collectors.joining(","));

List<ReadChildrenCategoryResponse> categories = ReadChildrenCategoryListResponse
.fromEntity(categoryRepository.findLeafCategories())
.getCategoryResponses();

String allCategories = categories.stream()
.map(ReadChildrenCategoryResponse::toString)
.collect(Collectors.joining(","));

Gender[] genders = Gender.values();
String genderStrings = Arrays.toString(genders);

List<Map<String, Object>> messages = new ArrayList<>();
Map<String, Object> message = new HashMap<>();
message.put("role", "user");
message.put("content", createPrompt(searchQuery, allBrands, allCategories, genderStrings));
messages.add(message);

Map<String, Object> requestData = new HashMap<>();
requestData.put("model", "gpt-3.5-turbo-1106");
requestData.put("messages", messages);
requestData.put("temperature", 0.3);
requestData.put("max_tokens", 300);

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(environment.getProperty("open-ai.secret-key"));

String requestBody = objectMapper.writeValueAsString(requestData);

HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

String apiEndpoint = "https://api.openai.com/v1/chat/completions";
Object result = restTemplate.postForObject(apiEndpoint, entity, Object.class);
return objectMapper.writeValueAsString(result);
}

private String createPrompt(String searchQuery, String brands, String categories, String genders) {
return "{" +
"\"categories\": [" + categories + "], " +
"\"brands\": [" + brands + "], " +
"\"genders\": " + genders + ", " +
"\"priceRanges\": [" +
"{\"id\": 1, \"name\": \"$0-$99\"}, " +
"{\"id\": 2, \"name\": \"$100-$199\"}, " +
"{\"id\": 3, \"name\": \"$200-$299\"}, " +
"{\"id\": 4, \"name\": \"$300-$399\"}, " +
"{\"id\": 5, \"name\": \"over $400\"}" +
"], " +
"Search Query: \"" + searchQuery + "\"." +
"Based on the search query, let me know the relevant 3 categories, 3 brands, 1 gender, and 1 price range. " +
"Please provide the answer in the json object format. " +
"{\"categories\":[{\"id\":1, \"name\":\"Fashion\"}, {\"id\":2, \"name\":\"Electronics\"}, {\"id\":3, \"name\": \"Home & Living\"}], " +
"\"brands\":[{\"id\":1, \"name\":\"Nike\"}, {\"id\":2, \"name\":\"Samsung\"}, {\"id\":3, \"name\":\"Apple\"}], " +
"\"genders\":[\"MALE\"], " +
"\"priceRanges\":[{\"id\":1, \"name\":\"$0-$99\"}]}" +
"}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.dailyon.productservice.common.feign.response;

import com.dailyon.productservice.brand.entity.Brand;
import com.dailyon.productservice.category.entity.Category;
import com.dailyon.productservice.common.enums.Gender;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.List;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OpenAIResponse {
private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;
@JsonProperty("system_fingerprint")
private String systemFingerprint;

@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class Choice {
private int index;
private Message message;
private Object logprobs;
@JsonProperty("finish_reason")
private String finishReason;
}

@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class Message {
private String role;
private String content;
}

@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class Content {
private List<ReadChildrenCategoryResponse> categories;
private List<ReadBrandResponse> brands;
private List<Gender> genders;
private List<PriceRange> priceRanges;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ReadChildrenCategoryResponse {
private Long id;
private String name;

@Override
public String toString() {
return "{'id':" + this.id + ", 'name':" + "'" + this.name + "'" +"}";
}

public static ReadChildrenCategoryResponse fromEntity(Category category) {
return ReadChildrenCategoryResponse.builder()
.id(category.getId())
.name(category.getName())
.build();
}
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ReadBrandResponse {
private Long id;
private String name;

@Override
public String toString() {
return "{'id':" + this.id + ", 'name':" + "'" + this.name + "'" +"}";
}

public static ReadBrandResponse fromEntity(Brand brand) {
return ReadBrandResponse.builder()
.id(brand.getId())
.name(brand.getName())
.build();
}
}

@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class PriceRange {
private Long id;
private String name;
}

@Getter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class Usage {
@JsonProperty("prompt_tokens")
private Long promptTokens;

@JsonProperty("completion_tokens")
private Long completionTokens;

@JsonProperty("total_tokens")
private Long totalTokens;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,7 @@ ResponseEntity<ReadProductDetailResponse> readProductDetail(@PathVariable Long p
ResponseEntity<ReadProductDetailResponse> readAuctionProductDetail(@PathVariable Long productId) {
return ResponseEntity.status(HttpStatus.OK).body(productFacade.readAuctionProductDetail(productId));
}
/**
* 쇼핑몰 화면에서 무한 스크롤 위한 조회 api
* @param lastVal 최초 호출 시 direction이 asc면 0, desc면 큰 값
* @param type 상품 타입(NORMAL, AUCTION)
* @param brandId 브랜드 id
* @param categoryId 카테고리 id(카테고리 지정하면 하위 카테고리들 포함)
* @param gender 성별(MALE, FEMALE, COMMON)
* @param lowPrice 가격 필터 하한
* @param highPrice 가격 필터 상한
* @param sort 정렬 기준(price, review, rating)
* @param direction 오름/내림차순(asc, desc)
* @param query 상품명 또는 코드
* @return hasNext, List -> List의 마지막값을 lastVal에 저장하고 다음 요청에 포함시켜야 함.
*/

@GetMapping
ResponseEntity<ReadProductSliceResponse> readProductSlice(
@RequestParam(required = false, defaultValue = "NORMAL") ProductType type,
Expand All @@ -60,6 +47,11 @@ ResponseEntity<ReadProductSliceResponse> readProductSlice(
);
}

@GetMapping("/search")
ResponseEntity<ReadProductSearchResponse> searchProducts(@RequestParam String query) {
return ResponseEntity.status(HttpStatus.OK).body(productFacade.searchProducts(query));
}

@GetMapping("/search/ootd")
ResponseEntity<ReadOOTDSearchSliceResponse> searchProductsFromOOTD(@RequestParam Long lastId,
@RequestParam String query) {
Expand Down
Loading

0 comments on commit 157b966

Please sign in to comment.