-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: gpt module 추가 * feat: gpt 연동 * chore: 패키지명 peomfoot -> poemfoot 수정 * feat: GptPoemRequest 요청 구조 변경 * feat: Gpt 모듈 Swagger 설정 추가 * feat: CORS 관련 설정 추가 * chore: application.yml db 및 jpa 설정 추가 * feat: 예외처리 관련 설정 추가 * feat: BaseTime 관련 설정 추가 * chore: 디렉토리 구조 변경 * feat: gpt 질문 및 답변 구조 구현 * feat: 일일 gpt request 횟수 제한 기능 구현 * docs: gpt관련 환경변수 추가 * style: 문자 join 방식 변경 * style: 구조 변경 * chore: 패키지 구조 변경 * feat: jpa-repository 관련 로직 api 모듈에서 실행되도록 구현 * test: gpt 모듈 관련 테스트 임시 비활성화 처리 * fix: 재사용 시에도 질문 및 응답이 저장되는 문제 해결 GptChatPoemResponse에 reuse변수 추가하였습니다. * feat: gptRequestCount관련 예외 처리 추가 너무 많은 요청이 들어와 count가 제대로 되지 않는 경우 처리했습니다. * feat: gptRequestCount 동시성 예외 처리 * chore: BaseTime 패키지 위치 이동 * fix: test용 gpt api-key값 추가 * test: GptAnswerService 관련 테스트 추가 * test: GptQuestionService 관련 테스트 추가 * fix: bootJar 관련 버그 수정 main 함수 추가 --------- Co-authored-by: Shinyoung Kim <[email protected]>
- Loading branch information
1 parent
19ac696
commit 75467cf
Showing
39 changed files
with
867 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
subprojects/api/src/main/java/com/poemfoot/api/domain/BaseTime.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.poemfoot.api.domain; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.EntityListeners; | ||
import jakarta.persistence.MappedSuperclass; | ||
import java.sql.Timestamp; | ||
import lombok.Getter; | ||
import org.springframework.data.annotation.CreatedDate; | ||
import org.springframework.data.annotation.LastModifiedDate; | ||
import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
||
@Getter | ||
@MappedSuperclass | ||
@EntityListeners(AuditingEntityListener.class) | ||
public class BaseTime { | ||
@CreatedDate | ||
@Column(name = "created_time", updatable = false) | ||
private Timestamp createdTime; | ||
|
||
@LastModifiedDate | ||
@Column(name = "modified_time") | ||
private Timestamp modifiedTime; | ||
} |
103 changes: 103 additions & 0 deletions
103
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/controller/ControllerAdvice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package com.poemfoot.api.domain.gpt.controller; | ||
|
||
import com.poemfoot.api.global.dto.error.ErrorResponse; | ||
import com.poemfoot.gpt.exception.GptException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import java.util.Objects; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.tomcat.util.http.fileupload.FileUploadException; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.http.converter.HttpMessageNotReadableException; | ||
import org.springframework.validation.FieldError; | ||
import org.springframework.web.HttpMediaTypeException; | ||
import org.springframework.web.HttpRequestMethodNotSupportedException; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
import org.springframework.web.bind.MissingServletRequestParameterException; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@RestControllerAdvice | ||
public class ControllerAdvice { | ||
|
||
private static final int FIELD_ERROR_CODE_INDEX = 0; | ||
private static final int FIELD_ERROR_MESSAGE_INDEX = 1; | ||
|
||
@ExceptionHandler(MethodArgumentNotValidException.class) | ||
public ResponseEntity<ErrorResponse> handleInputFieldException(MethodArgumentNotValidException e) { | ||
FieldError mainError = e.getFieldErrors().get(0); | ||
String[] errorInfo = Objects.requireNonNull(mainError.getDefaultMessage()).split(":"); | ||
|
||
int code = Integer.parseInt(errorInfo[FIELD_ERROR_CODE_INDEX]); | ||
String message = errorInfo[FIELD_ERROR_MESSAGE_INDEX]; | ||
|
||
return ResponseEntity.badRequest().body(new ErrorResponse(code, message)); | ||
} | ||
|
||
@ExceptionHandler(HttpMessageNotReadableException.class) | ||
public ResponseEntity<ErrorResponse> handleJsonException(HttpMessageNotReadableException e) { | ||
log.warn("Json Exception ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9000, "Json 형식이 올바르지 않습니다.")); | ||
} | ||
|
||
@ExceptionHandler(HttpMediaTypeException.class) | ||
public ResponseEntity<ErrorResponse> handleContentTypeException(HttpMediaTypeException e) { | ||
log.warn("ContentType Exception ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9001, "ContentType 값이 올바르지 않습니다.")); | ||
} | ||
|
||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class) | ||
public ResponseEntity<ErrorResponse> handleRequestMethodException(HttpRequestMethodNotSupportedException e) { | ||
log.warn("Http Method not supported Exception ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9002, "해당 Http Method에 맞는 API가 존재하지 않습니다.")); | ||
} | ||
|
||
@ExceptionHandler(MissingServletRequestParameterException.class) | ||
public ResponseEntity<ErrorResponse> handleMissingRequestParamException(MissingServletRequestParameterException e) { | ||
log.warn("Request Param is Missing! ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9003, "요청 param 이름이 올바르지 않습니다.")); | ||
} | ||
|
||
@ExceptionHandler(FileUploadException.class) | ||
public ResponseEntity<ErrorResponse> handleMissingMultiPartParamException(FileUploadException e) { | ||
log.warn("File Upload Exception! Please check request. ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9004, "요청 파일이 올바르지 않습니다. 파일 손상 여부나 요청 형식을 확인해주세요.")); | ||
} | ||
|
||
@ExceptionHandler(MethodArgumentTypeMismatchException.class) | ||
public ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { | ||
log.warn("Invalid date format! Please provide the date in the format yyyy-MM-dd. ErrMessage={}\n", e.getMessage()); | ||
|
||
return ResponseEntity.badRequest() | ||
.body(new ErrorResponse(9005, "올바른 날짜 형식으로 입력해주세요.")); | ||
} | ||
|
||
@ExceptionHandler(GptException.class) | ||
public ResponseEntity<ErrorResponse> handleGptException(GptException e) { | ||
return ResponseEntity.status(e.getHttpStatus()).body(new ErrorResponse(e.getCode(), e.getMessage())); | ||
} | ||
|
||
@ExceptionHandler(Exception.class) | ||
public ResponseEntity<ErrorResponse> unhandledException(Exception e, HttpServletRequest request) { | ||
log.error("UnhandledException: {} {} errMessage={}\n", | ||
request.getMethod(), | ||
request.getRequestURI(), | ||
e.getMessage() | ||
); | ||
return ResponseEntity.internalServerError() | ||
.body(new ErrorResponse(9999, "일시적으로 접속이 원활하지 않습니다. 서버 팀에 문의 부탁드립니다.")); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/controller/GptPoemController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.poemfoot.api.domain.gpt.controller; | ||
|
||
import com.poemfoot.api.domain.gpt.domain.GptAnswer; | ||
import com.poemfoot.api.domain.gpt.service.GptAnswerService; | ||
import com.poemfoot.api.domain.gpt.service.GptQuestionService; | ||
import com.poemfoot.gpt.dto.request.GptChatPoemRequest; | ||
import com.poemfoot.gpt.dto.response.chat.GptChatPoemResponse; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@Tag(name = "GPT", description = "GPT 시 생성 관련 API") | ||
@RequestMapping("/api/v1/gpt") | ||
public class GptPoemController { | ||
|
||
private final GptQuestionService gptQuestionService; | ||
private final GptAnswerService gptAnswerService; | ||
|
||
@Operation(summary = "Gpt 시 생성") | ||
@PostMapping("/completion/chat") | ||
public ResponseEntity<GptChatPoemResponse> completionChat( | ||
final @RequestBody GptChatPoemRequest request) { | ||
GptChatPoemResponse response = gptQuestionService.requestPoem(request); | ||
|
||
if (!response.isReuse()) { | ||
GptAnswer gptAnswer = gptAnswerService.saveAnswer(response); | ||
gptQuestionService.saveQuestion(request, gptAnswer); | ||
} | ||
return ResponseEntity.ok(response); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/domain/GptAnswer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.poemfoot.api.domain.gpt.domain; | ||
|
||
import com.poemfoot.api.domain.BaseTime; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class GptAnswer extends BaseTime { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(length = 500, nullable = false) | ||
private String answer; | ||
|
||
private String object; | ||
|
||
private String model; | ||
|
||
public GptAnswer(String answer, String object, String model) { | ||
this.answer = answer; | ||
this.object = object; | ||
this.model = model; | ||
} | ||
|
||
|
||
} |
37 changes: 37 additions & 0 deletions
37
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/domain/GptQuestion.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.poemfoot.api.domain.gpt.domain; | ||
|
||
import com.poemfoot.api.domain.BaseTime; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.Table; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Table(name = "gpt_question") | ||
public class GptQuestion extends BaseTime { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(name = "question", nullable = false) | ||
private String question; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "answer_id") | ||
private GptAnswer answer; | ||
|
||
public GptQuestion(String question, GptAnswer answer) { | ||
this.question = question; | ||
this.answer = answer; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...rojects/api/src/main/java/com/poemfoot/api/domain/gpt/repository/GptAnswerRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.poemfoot.api.domain.gpt.repository; | ||
|
||
import com.poemfoot.api.domain.gpt.domain.GptAnswer; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface GptAnswerRepository extends JpaRepository<GptAnswer,Long> { | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
...jects/api/src/main/java/com/poemfoot/api/domain/gpt/repository/GptQuestionRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.poemfoot.api.domain.gpt.repository; | ||
|
||
import com.poemfoot.api.domain.gpt.domain.GptQuestion; | ||
import java.util.Optional; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface GptQuestionRepository extends JpaRepository<GptQuestion, Long> { | ||
|
||
Optional<GptQuestion> findFirstByQuestion(String question); | ||
} |
38 changes: 38 additions & 0 deletions
38
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/service/GptAnswerService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.poemfoot.api.domain.gpt.service; | ||
|
||
import com.poemfoot.api.domain.gpt.domain.GptAnswer; | ||
import com.poemfoot.api.domain.gpt.repository.GptAnswerRepository; | ||
import com.poemfoot.gpt.dto.response.chat.GptChatPoemResponse; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class GptAnswerService { | ||
|
||
private final GptAnswerRepository gptAnswerRepository; | ||
|
||
@Transactional | ||
public GptAnswer saveAnswer(GptChatPoemResponse response) { | ||
|
||
return gptAnswerRepository.save(new GptAnswer(getAnswer(response), response.getObject(), | ||
response.getObject())); | ||
} | ||
|
||
private String getAnswer(GptChatPoemResponse response) { | ||
return responseToMessage(response).stream() | ||
.filter(Objects::nonNull) | ||
.collect(Collectors.joining()); | ||
} | ||
|
||
private List<String> responseToMessage(GptChatPoemResponse response) { | ||
return response.getMessages().stream() | ||
.toList(); | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
subprojects/api/src/main/java/com/poemfoot/api/domain/gpt/service/GptQuestionService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.poemfoot.api.domain.gpt.service; | ||
|
||
import com.poemfoot.api.domain.gpt.domain.GptAnswer; | ||
import com.poemfoot.api.domain.gpt.domain.GptQuestion; | ||
import com.poemfoot.api.domain.gpt.repository.GptQuestionRepository; | ||
import com.poemfoot.gpt.dto.request.GptChatPoemRequest; | ||
import com.poemfoot.gpt.dto.response.chat.GptChatPoemResponse; | ||
import com.poemfoot.gpt.exception.badrequest.GptOverRequestException; | ||
import com.poemfoot.gpt.exception.toomanyrequest.GptTooManyRequestException; | ||
import com.poemfoot.gpt.service.GptPoemProvider; | ||
|
||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
public class GptQuestionService { | ||
|
||
private final GptPoemProvider gptPoemProvider; | ||
private final GptQuestionRepository gptQuestionRepository; | ||
|
||
@Transactional | ||
public GptChatPoemResponse requestPoem(GptChatPoemRequest request) { | ||
Optional<GptQuestion> question = gptQuestionRepository | ||
.findFirstByQuestion(getQuestion(request)); | ||
|
||
if (question.isPresent()) { | ||
GptAnswer answer = question.get().getAnswer(); | ||
return GptChatPoemResponse.of( | ||
answer.getId().toString(), | ||
answer.getObject(), | ||
answer.getModel(), | ||
answer.getAnswer()); | ||
} | ||
return gptPoemProvider.completionChat(request) | ||
.orElseThrow(GptTooManyRequestException::new); | ||
} | ||
|
||
@Transactional | ||
public GptQuestion saveQuestion(GptChatPoemRequest request, GptAnswer answer) { | ||
return gptQuestionRepository.save(new GptQuestion(getQuestion(request), answer)); | ||
} | ||
|
||
private String getQuestion(GptChatPoemRequest request) { | ||
return Stream.concat(request.getWords().stream(), Stream.of(request.getLocation())) | ||
.collect(Collectors.joining(",")); | ||
} | ||
} |
Oops, something went wrong.