From 2609287f7eace53291e886964bb5e1081dce6d81 Mon Sep 17 00:00:00 2001 From: Sujin Kim <108571492+cowboysj@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:55:41 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20ChatGPT=20API=20=EC=97=B0=EB=8F=99?= =?UTF-8?q?=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 프로젝트 환경 설정 및 DDD 아키텍처 예시 패키지 및 클래스 작성 (#2) * feat: 프로젝트 환경 설정 * feat: DDD 아키텍처 예시 패키지 및 클래스 작성 * chore: Spotless 코드 포맷팅 + pre-commit 스크립트 작성 (#4) chore: Spotless 코드 포맷팅 + pre-commit 스크립트 작성 (#4) * chore : spotless 설정 * chore : spotless 컨벤션 적용 * chore : spotless pre-commit 적용 * chore : spotless 자바 포맷 변경 * chore : 자바 컨벤션 적용 [Chore] Spotless 코드 포맷팅 + pre-commit 스크립트 작성 (#4) * chore : spotless 설정 * chore : spotless 컨벤션 적용 * chore : spotless pre-commit 적용 * chore : spotless 자바 포맷 변경 * chore : 자바 컨벤션 적용 * chore: 스웨거 세팅 (#8) * chore : 스웨거 세팅 * chore : 스웨거 세팅 * chore : application.yml에 스웨거 설정 추가 * chore : 스웨거 버전 2.1 -> 2.8.4로 변경 * chore: 개발 서버 docker compose 기반 환경 구축 및 배포 파이프라인 작성 (#10) * chore: pr open 시 빌드 체크 워크플로 추가 * chore: 개발 서버 docker-compose.yml 추가 * chore: Dockerfile 작성 * chore: 개발 서버 배포 파이프라인 작성 * chore: docker login 최신 버전 적용 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: 테스트를 위해 임시 배포 조건 설정 * chore: 개행 추가 * chore: docker-compose.yml 잘못된 경로 수정 * chore: 배포 테스트를 위한 on pull_request 조건 제거 * chore: compileJava로 변경 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore : 이슈 템플릿 수정 (#11) * docs : 이슈 템플릿 생성 * docs : 이슈 템플릿 작성 * docs : 이슈 템플릿 작성 * docs : 이슈 템플릿 작성 * docs : 이슈 템플릿 작성 * docs : 이슈 템플릿 작성 * chore : 이슈 템플릿 설정 * feat : 글로벌 에러 핸들러 설정 (#13) (#14) * feat : 글로벌 에러 핸들러 설정 (#13) * feat : PR 리뷰 수정 * feat : 컨벤션에 맞게 수정 * feat : entity 설계 (#16) * feat : entity 설계 * feat : gradle 수정 * feat : 리뷰 사항 수정 * chore : 포트폴리오 패키지 정리 * chore : 포트폴리오 패키지 정리 * feat : ChatGPT 프로퍼티 추가 * feat : ChatGPT 프로퍼티 추가 * feat : RestClient를 이용해 ChatGPT API 요청 구현 * feat : RestClient를 이용해 ChatGPT API 요청 구현 * chore : 파일 정리 * chore : 파일 정리 * feat : chatGPT API 연동 * feat : chatGPT API 연동 * chore : gitignore 설정 * chore : gitignore 설정 * chore : 파일 정리 * feat : response 스키마 추가 * feat : ChatGPT 서비스 수정 * chore : 파일 정리 * feat : static import, 기타 코드 리뷰 반영 * feat : static import, 기타 코드 리뷰 반영 --------- Co-authored-by: 이한음 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Choi Tae gyu <39821474+suker80@users.noreply.github.com> --- .gitignore | 43 ++++++++++++ build.gradle | 3 + .../common/config/PropertiesConfig.java | 6 ++ .../common/config/RestClientConfig.java | 19 ++++++ .../application/FeedbackCommandService.java | 61 +++++++++++++++++ .../FeedbackCommandExceptionCode.java | 4 ++ .../domain/FeedbackCommandRepository.java | 7 ++ .../command/infrastructure/ChatGPTClient.java | 27 ++++++++ .../infrastructure/ChatGPTConstants.java | 8 +++ .../infrastructure/ChatGPTProperties.java | 7 ++ .../infrastructure/ChatGPTService.java | 67 +++++++++++++++++++ .../FeedbackCommandRepositoryImpl.java | 8 +++ .../FeedbackCommandController.java | 32 +++++++++ .../presentation/request/ChatGPTRequest.java | 24 +++++++ .../response/ChatGPTResponse.java | 8 +++ .../response/OverallFeedbackResponse.java | 34 ++++++++++ .../response/ProjectFeedbackResponse.java | 67 +++++++++++++++++++ .../application/FeedbackQueryService.java | 7 ++ .../exception/FeedbackQueryExceptionCode.java | 4 ++ .../query/domain/FeedbackQueryRepository.java | 7 ++ .../FeedbackQueryRepositoryImpl.java | 8 +++ .../presentation/FeedbackQueryController.java | 8 +++ .../query/presentation/request/.gitkeep | 0 .../query/presentation/response/.gitkeep | 0 .../PortfolioDomainExceptionCode.java | 3 + src/main/resources/application.yml | 7 +- 26 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 src/main/java/depromeet/onepiece/common/config/PropertiesConfig.java create mode 100644 src/main/java/depromeet/onepiece/common/config/RestClientConfig.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/application/FeedbackCommandService.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/application/exception/FeedbackCommandExceptionCode.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/domain/FeedbackCommandRepository.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTClient.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTConstants.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTProperties.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTService.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/infrastructure/FeedbackCommandRepositoryImpl.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/presentation/FeedbackCommandController.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/presentation/request/ChatGPTRequest.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/presentation/response/ChatGPTResponse.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/presentation/response/OverallFeedbackResponse.java create mode 100644 src/main/java/depromeet/onepiece/feedback/command/presentation/response/ProjectFeedbackResponse.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/application/FeedbackQueryService.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/application/exception/FeedbackQueryExceptionCode.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/domain/FeedbackQueryRepository.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/infrastructure/FeedbackQueryRepositoryImpl.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/presentation/FeedbackQueryController.java create mode 100644 src/main/java/depromeet/onepiece/feedback/query/presentation/request/.gitkeep create mode 100644 src/main/java/depromeet/onepiece/feedback/query/presentation/response/.gitkeep create mode 100644 src/main/java/depromeet/onepiece/portfolio/domain/exception/PortfolioDomainExceptionCode.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e22eca1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Java 관련 +*.class +*.war +*.jar + +# IDE 설정 파일 +/.idea/ +/.vscode/ +/.project +/.classpath +/.settings/ + +# OS 관련 +.DS_Store +Thumbs.db + +# 환경 변수 및 보안 파일 +.env +config/*.yml +config/*.properties +src/main/resources/application-*.yml +src/main/resources/application-*.properties + +# 로그 및 빌드 파일 +/logs/ +*.log +/build/ +/target/ + +# Git 관련 +*.swp +*.swo +*.bak + +# IntelliJ 관련 +*.iml +*.ipr +*.iws + +# 기타 +*.~ +*.tmp +*.backup diff --git a/build.gradle b/build.gradle index 370e927..8f6c800 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,9 @@ dependencies { implementation 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // HTTP CLIENT + implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1' + // MONGO DB implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' diff --git a/src/main/java/depromeet/onepiece/common/config/PropertiesConfig.java b/src/main/java/depromeet/onepiece/common/config/PropertiesConfig.java new file mode 100644 index 0000000..25ba74b --- /dev/null +++ b/src/main/java/depromeet/onepiece/common/config/PropertiesConfig.java @@ -0,0 +1,6 @@ +package depromeet.onepiece.common.config; + +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationPropertiesScan +public class PropertiesConfig {} diff --git a/src/main/java/depromeet/onepiece/common/config/RestClientConfig.java b/src/main/java/depromeet/onepiece/common/config/RestClientConfig.java new file mode 100644 index 0000000..548d217 --- /dev/null +++ b/src/main/java/depromeet/onepiece/common/config/RestClientConfig.java @@ -0,0 +1,19 @@ +package depromeet.onepiece.common.config; + +import depromeet.onepiece.feedback.command.infrastructure.ChatGPTConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +@Configuration +public class RestClientConfig { + private static final String API_URL = ChatGPTConstants.API_URL; + + @Bean + public RestClient restClient() { + return RestClient.builder() + .baseUrl(API_URL) + .defaultHeader("Content-Type", "application/json") + .build(); + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/application/FeedbackCommandService.java b/src/main/java/depromeet/onepiece/feedback/command/application/FeedbackCommandService.java new file mode 100644 index 0000000..1b423d2 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/application/FeedbackCommandService.java @@ -0,0 +1,61 @@ +package depromeet.onepiece.feedback.command.application; + +import depromeet.onepiece.feedback.command.infrastructure.ChatGPTService; +import depromeet.onepiece.feedback.command.presentation.request.ChatGPTRequest; +import depromeet.onepiece.feedback.command.presentation.response.OverallFeedbackResponse; +import depromeet.onepiece.feedback.command.presentation.response.ProjectFeedbackResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class FeedbackCommandService { + private final ChatGPTService chatGPTService; + + public OverallFeedbackResponse overallFeedback(String portfolioId) { + // portfolioId 검증 + // portfolioId로 포트폴리오 조회 + // 포트폴리오 이미지 추출 + // chatgptservice의 overallfeedback 포트폴리오 이미지 담아서 호출 + ChatGPTRequest requestDto = createOverallFeedbackRequest(portfolioId); + return chatGPTService.overallFeedback(requestDto); + } + + public ProjectFeedbackResponse projectFeedback(String portfolioId) { + // portfolioId 검증 + // portfolioId로 포트폴리오 조회 + // 포트폴리오 이미지 추출 + // chatgptservice의 projectfeedback 포트폴리오 이미지 담아서 호출 + ChatGPTRequest requestDto = createProjectFeedbackRequest(portfolioId); + return chatGPTService.projectFeedback(requestDto); + } + + private ChatGPTRequest createOverallFeedbackRequest(String portfolioId) { + return new ChatGPTRequest( + "gpt-4", + List.of( + new ChatGPTRequest.Message( + "user", List.of(new ChatGPTRequest.Message.Content("text", "프롬프트", null)))), + new ChatGPTRequest.ResponseFormat("json", null), + 0.7, + 5000, + 1.0, + 0, + 0); + } + + private ChatGPTRequest createProjectFeedbackRequest(String portfolioId) { + return new ChatGPTRequest( + "gpt-4", + List.of( + new ChatGPTRequest.Message( + "user", List.of(new ChatGPTRequest.Message.Content("text", "프롬프트", null)))), + new ChatGPTRequest.ResponseFormat("json", null), + 0.8, + 5000, + 1.0, + 0, + 0); + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/application/exception/FeedbackCommandExceptionCode.java b/src/main/java/depromeet/onepiece/feedback/command/application/exception/FeedbackCommandExceptionCode.java new file mode 100644 index 0000000..3ce85e5 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/application/exception/FeedbackCommandExceptionCode.java @@ -0,0 +1,4 @@ +package depromeet.onepiece.feedback.command.application.exception; + +/** CommandExceptionCode는 CommandService에서 발생하는 커스텀 예외 코드를 정의해요. */ +public enum FeedbackCommandExceptionCode {} diff --git a/src/main/java/depromeet/onepiece/feedback/command/domain/FeedbackCommandRepository.java b/src/main/java/depromeet/onepiece/feedback/command/domain/FeedbackCommandRepository.java new file mode 100644 index 0000000..4dc03d3 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/domain/FeedbackCommandRepository.java @@ -0,0 +1,7 @@ +package depromeet.onepiece.feedback.command.domain; + +/** + * 도메인 주도 개발 DDD 아키텍처에서는 CommandRepository 별도로 구현하여 CQRS 패턴을 적용합니다. Master-Slave 구조에서는 Master DB에 쓰기 + * 작업을 하고, Slave DB에서 읽기 작업을 하기 때문에, CommandRepository를 별도로 구현해요. + */ +public interface FeedbackCommandRepository {} diff --git a/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTClient.java b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTClient.java new file mode 100644 index 0000000..cf1dc08 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTClient.java @@ -0,0 +1,27 @@ +package depromeet.onepiece.feedback.command.infrastructure; + +import static depromeet.onepiece.feedback.command.infrastructure.ChatGPTConstants.API_URL; + +import depromeet.onepiece.feedback.command.presentation.request.ChatGPTRequest; +import depromeet.onepiece.feedback.command.presentation.response.ChatGPTResponse; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +@Component +@AllArgsConstructor +public class ChatGPTClient { + private final RestClient restClient; + private final ChatGPTProperties chatGPTProperties; + + public ChatGPTResponse sendMessage(ChatGPTRequest request) { + return restClient + .post() + .uri(API_URL) + .header("Authorization", "Bearer " + chatGPTProperties.apiKey()) + .header("Content-Type", "application/json") + .body(request) + .retrieve() + .body(ChatGPTResponse.class); + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTConstants.java b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTConstants.java new file mode 100644 index 0000000..421731a --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTConstants.java @@ -0,0 +1,8 @@ +package depromeet.onepiece.feedback.command.infrastructure; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public final class ChatGPTConstants { + public static final String API_URL = "https://api.openai.com/v1/chat/completions"; +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTProperties.java b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTProperties.java new file mode 100644 index 0000000..f5cd981 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTProperties.java @@ -0,0 +1,7 @@ +package depromeet.onepiece.feedback.command.infrastructure; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "chatgpt") +public record ChatGPTProperties( + String apiKey, String model, Double temperature, Integer maxTokens) {} diff --git a/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTService.java b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTService.java new file mode 100644 index 0000000..725d05c --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/ChatGPTService.java @@ -0,0 +1,67 @@ +package depromeet.onepiece.feedback.command.infrastructure; + +import depromeet.onepiece.feedback.command.presentation.request.ChatGPTRequest; +import depromeet.onepiece.feedback.command.presentation.response.ChatGPTResponse; +import depromeet.onepiece.feedback.command.presentation.response.OverallFeedbackResponse; +import depromeet.onepiece.feedback.command.presentation.response.ProjectFeedbackResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChatGPTService { + private final ChatGPTClient chatGPTClient; + + public OverallFeedbackResponse overallFeedback(ChatGPTRequest requestDto) { + ChatGPTRequest updatedRequest = + new ChatGPTRequest( + requestDto.model(), + requestDto.messages(), + requestDto.response_format(), + 0.7, + 5000, + 1.0, + 0, + 0); + + ChatGPTResponse response = chatGPTClient.sendMessage(updatedRequest); + return mapToOverallFeedback(response); + } + + public ProjectFeedbackResponse projectFeedback(ChatGPTRequest requestDto) { + ChatGPTRequest updatedRequest = + new ChatGPTRequest( + requestDto.model(), + requestDto.messages(), + requestDto.response_format(), + 0.8, + 5000, + 1.0, + 0, + 0); + + ChatGPTResponse response = chatGPTClient.sendMessage(updatedRequest); + return mapToProjectFeedback(response); + } + + // TODO: response 수정 후 매핑 + private OverallFeedbackResponse mapToOverallFeedback(ChatGPTResponse response) { + return new OverallFeedbackResponse( + new OverallFeedbackResponse.OverallEvaluation( + response.response(), + new OverallFeedbackResponse.EvaluationDetail(80, "직무 적합성이 높습니다."), + new OverallFeedbackResponse.EvaluationDetail(75, "논리적인 사고력이 돋보입니다."), + new OverallFeedbackResponse.EvaluationDetail(85, "글쓰기 표현이 명확합니다."), + new OverallFeedbackResponse.EvaluationDetail(90, "레이아웃이 가독성이 높습니다.")), + List.of(new OverallFeedbackResponse.FeedbackDetail("창의적인 디자인", "다양한 시각적 요소 활용이 뛰어남")), + List.of(new OverallFeedbackResponse.FeedbackDetail("문장 간결화 필요", "텍스트를 조금 더 다듬으면 좋습니다."))); + } + + // TODO: response 수정 후 매핑 + private ProjectFeedbackResponse mapToProjectFeedback(ChatGPTResponse response) { + return new ProjectFeedbackResponse( + new ProjectFeedbackResponse.ProjectEvaluation( + "프로젝트 이름", "https://example.com/image.png", null, "프로세스 리뷰", "강점 분석", "개선 사항", null)); + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/infrastructure/FeedbackCommandRepositoryImpl.java b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/FeedbackCommandRepositoryImpl.java new file mode 100644 index 0000000..d39ef81 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/infrastructure/FeedbackCommandRepositoryImpl.java @@ -0,0 +1,8 @@ +package depromeet.onepiece.feedback.command.infrastructure; + +/** + * RepositoryImpl은 Repository 인터페이스를 구현한 클래스로, 실제로 데이터베이스에 접근하여 데이터를 저장하거나 조회하는 역할을 해요. 데이터베이스 접근 + * 기술(JPA DATA, QueryDSL, MyBatis 등)을 사용하여 구현합니다. 만약 JPA 및 QueryDSL을 사용한다면, infrastructure 패키지 내에 + * PaymentCommandJpaRepository, PaymentCommandQueryDslRepository 같이 구현 클래스를 생성할 수 있어요. + */ +public class FeedbackCommandRepositoryImpl {} diff --git a/src/main/java/depromeet/onepiece/feedback/command/presentation/FeedbackCommandController.java b/src/main/java/depromeet/onepiece/feedback/command/presentation/FeedbackCommandController.java new file mode 100644 index 0000000..86fd827 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/presentation/FeedbackCommandController.java @@ -0,0 +1,32 @@ +package depromeet.onepiece.feedback.command.presentation; + +import depromeet.onepiece.feedback.command.application.FeedbackCommandService; +import depromeet.onepiece.feedback.command.presentation.response.OverallFeedbackResponse; +import depromeet.onepiece.feedback.command.presentation.response.ProjectFeedbackResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Portfolio", description = "포트폴리오 관련 API") +@RestController +@RequestMapping("/api/v1/portfolio") +@RequiredArgsConstructor +public class FeedbackCommandController { + private final FeedbackCommandService portfolioCommandService; + + @Operation(summary = "포트폴리오 종합평가 반환", description = "종합평가를 반환하는 API") + @GetMapping("/overall") + public OverallFeedbackResponse overallFeedback(@RequestParam String portfolioId) { + return portfolioCommandService.overallFeedback(portfolioId); + } + + @Operation(summary = "포트폴리오 프로젝트 별 피드백 반환", description = "프로젝트 별 피드백을 반환하는 API") + @GetMapping("/project") + public ProjectFeedbackResponse projectFeedback(@RequestParam String portfolioId) { + return portfolioCommandService.projectFeedback(portfolioId); + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/presentation/request/ChatGPTRequest.java b/src/main/java/depromeet/onepiece/feedback/command/presentation/request/ChatGPTRequest.java new file mode 100644 index 0000000..7fd442e --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/presentation/request/ChatGPTRequest.java @@ -0,0 +1,24 @@ +package depromeet.onepiece.feedback.command.presentation.request; + +import java.util.List; +import java.util.Map; + +public record ChatGPTRequest( + String model, + List messages, + ResponseFormat response_format, + Double temperature, + Integer max_completion_tokens, + Double top_p, + Integer frequency_penalty, + Integer presence_penalty) { + public record Message(String role, List content) { + public record Content(String type, String text, ImageUrl image_url) { + public record ImageUrl(String url) {} + } + } + + public record ResponseFormat(String type, JsonSchema json_schema) { + public record JsonSchema(String name, Boolean strict, Map schema) {} + } +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ChatGPTResponse.java b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ChatGPTResponse.java new file mode 100644 index 0000000..c46c4f7 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ChatGPTResponse.java @@ -0,0 +1,8 @@ +package depromeet.onepiece.feedback.command.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record ChatGPTResponse( + @Schema(description = "챗봇 응답", requiredMode = REQUIRED) String response) {} diff --git a/src/main/java/depromeet/onepiece/feedback/command/presentation/response/OverallFeedbackResponse.java b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/OverallFeedbackResponse.java new file mode 100644 index 0000000..70a941d --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/OverallFeedbackResponse.java @@ -0,0 +1,34 @@ +package depromeet.onepiece.feedback.command.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +public record OverallFeedbackResponse( + @Schema(description = "전체 평가", requiredMode = REQUIRED) OverallEvaluation overallEvaluation, + @Schema(description = "강점 분석", requiredMode = REQUIRED) List strengths, + @Schema(description = "개선할 점 및 해결방안", requiredMode = REQUIRED) + List improvements) { + + public record OverallEvaluation( + @Schema(description = "종합 평가 요약", requiredMode = REQUIRED) String summary, + @Schema(description = "직무 적합성", requiredMode = REQUIRED) EvaluationDetail jobFit, + @Schema(description = "논리적 사고 평가", requiredMode = REQUIRED) EvaluationDetail logicalThinking, + @Schema(description = "문장 가독성 평가", requiredMode = REQUIRED) EvaluationDetail writingClarity, + @Schema(description = "레이아웃 가독성 평가", requiredMode = REQUIRED) + EvaluationDetail layoutReadability) {} + + public record EvaluationDetail( + @Schema(description = "점수 (0-100 범위)", requiredMode = REQUIRED) int score, + @Schema(description = "평가", requiredMode = REQUIRED) String review) {} + + public record FeedbackDetail( + @Schema(description = "제목", example = "스토리텔링이 강한 포트폴리오", requiredMode = REQUIRED) + String title, + @Schema( + description = "내용", + example = "단순한 디자인을 넘어서는 창의적인 아이디어가 돋보입니다.", + requiredMode = REQUIRED) + String content) {} +} diff --git a/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ProjectFeedbackResponse.java b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ProjectFeedbackResponse.java new file mode 100644 index 0000000..d89707a --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/command/presentation/response/ProjectFeedbackResponse.java @@ -0,0 +1,67 @@ +package depromeet.onepiece.feedback.command.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@Schema(description = "프로젝트 피드백 응답 DTO") +public record ProjectFeedbackResponse( + @Schema(description = "프로젝트 평가 정보", requiredMode = REQUIRED) + ProjectEvaluation projectEvaluation) { + + @Schema(description = "프로젝트 평가 정보") + public record ProjectEvaluation( + @Schema(description = "프로젝트 이름", requiredMode = REQUIRED) String projectName, + @Schema( + description = "프로젝트 이미지 링크", + example = "https://example.com/image.png", + requiredMode = REQUIRED) + String imageLink, + @Schema( + description = "프로젝트 프로세스 단계 완료 여부", + example = "[true, false, false, true, true]", + requiredMode = REQUIRED) + List process, + @Schema( + description = "프로세스 리뷰", + example = "프로젝트의 핵심 프로세스 중 일부가 잘 반영되었으며, 개선이 필요한 부분도 존재합니다.", + requiredMode = REQUIRED) + String processReview, + @Schema( + description = "강점 분석", + example = "# 1. 데이터 기반의 문제 해결\n- A/B 테스트를 통해 효과적인 UX를 도출.", + requiredMode = REQUIRED) + String strengths, + @Schema( + description = "개선 필요 사항", + example = "# 1. 문장이 너무 길고 가독성이 낮음\n- 내용을 압축하면 더 명확해질 수 있음.", + requiredMode = REQUIRED) + String areasForImprovement, + @Schema(description = "페이지별 이슈 리스트", requiredMode = REQUIRED) List pageIssues) {} + + @Schema(description = "페이지별 정보") + public record PageIssue( + @Schema(description = "페이지 번호", example = "1", requiredMode = REQUIRED) String pageNumber, + @Schema( + description = "이슈가 발생한 페이지 이미지 링크", + example = "https://example.com/page1.png", + requiredMode = REQUIRED) + String imageLink, + @Schema(description = "페이지 내 콘텐츠 이슈 리스트", requiredMode = REQUIRED) + List contents) {} + + @Schema(description = "페이지 콘텐츠 이슈 상세 정보") + public record PageContentIssue( + @Schema(description = "이슈 유형", example = "문장 오류", requiredMode = REQUIRED) String type, + @Schema( + description = "기존 문장", + example = "디자인의 다양함을 통해 다재다능함을 보여주는 디자이너입니다.", + requiredMode = REQUIRED) + String originalSentence, + @Schema( + description = "수정된 문장", + example = "다양한 디자인을 통해 다재다능함을 보여주는 디자이너입니다.", + requiredMode = REQUIRED) + String revisedSentence) {} +} diff --git a/src/main/java/depromeet/onepiece/feedback/query/application/FeedbackQueryService.java b/src/main/java/depromeet/onepiece/feedback/query/application/FeedbackQueryService.java new file mode 100644 index 0000000..403d12f --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/query/application/FeedbackQueryService.java @@ -0,0 +1,7 @@ +package depromeet.onepiece.feedback.query.application; + +/** + * 도메인 주도 개발 아키텍처에서 application 계층은 도메인 규칙에 핵심 로직 수행을 위임해요. 따라서 QueryService는 조회한 데이터를 DTO로 변환하거나, + * 여러 도메인 객체를 조합하여 응답 데이터를 만드는 역할을 합니다. + */ +public class FeedbackQueryService {} diff --git a/src/main/java/depromeet/onepiece/feedback/query/application/exception/FeedbackQueryExceptionCode.java b/src/main/java/depromeet/onepiece/feedback/query/application/exception/FeedbackQueryExceptionCode.java new file mode 100644 index 0000000..92f0854 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/query/application/exception/FeedbackQueryExceptionCode.java @@ -0,0 +1,4 @@ +package depromeet.onepiece.feedback.query.application.exception; + +/** QueryExceptionCode는 QueryService에서 발생하는 커스텀 예외 코드를 정의해요. */ +public enum FeedbackQueryExceptionCode {} diff --git a/src/main/java/depromeet/onepiece/feedback/query/domain/FeedbackQueryRepository.java b/src/main/java/depromeet/onepiece/feedback/query/domain/FeedbackQueryRepository.java new file mode 100644 index 0000000..0bddfb9 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/query/domain/FeedbackQueryRepository.java @@ -0,0 +1,7 @@ +package depromeet.onepiece.feedback.query.domain; + +/** + * 도메인 주도 개발 DDD 아키텍처에서는 조회 성능 향상을 위해 QueryRepository를 별도로 구현하여 CQRS 패턴을 적용합니다. Master-Slave 구조에서는 + * Master DB에 쓰기 작업을 하고, Slave DB에서 읽기 작업을 하기 때문에, QueryRepository를 별도로 구현해요. + */ +public interface FeedbackQueryRepository {} diff --git a/src/main/java/depromeet/onepiece/feedback/query/infrastructure/FeedbackQueryRepositoryImpl.java b/src/main/java/depromeet/onepiece/feedback/query/infrastructure/FeedbackQueryRepositoryImpl.java new file mode 100644 index 0000000..c3c00ba --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/query/infrastructure/FeedbackQueryRepositoryImpl.java @@ -0,0 +1,8 @@ +package depromeet.onepiece.feedback.query.infrastructure; + +/** + * RepositoryImpl은 Repository 인터페이스를 구현한 클래스로, 실제로 데이터베이스에 접근하여 데이터를 저장하거나 조회하는 역할을 해요. 데이터베이스 접근 + * 기술(JPA DATA, QueryDSL, MyBatis 등)을 사용하여 구현합니다. 만약 JPA 및 QueryDSL을 사용한다면, infrastructure 패키지 내에 + * PaymentQueryJpaRepository, PaymentQueryQueryDslRepository 같이 구현 클래스를 생성할 수 있어요. + */ +public class FeedbackQueryRepositoryImpl {} diff --git a/src/main/java/depromeet/onepiece/feedback/query/presentation/FeedbackQueryController.java b/src/main/java/depromeet/onepiece/feedback/query/presentation/FeedbackQueryController.java new file mode 100644 index 0000000..47db901 --- /dev/null +++ b/src/main/java/depromeet/onepiece/feedback/query/presentation/FeedbackQueryController.java @@ -0,0 +1,8 @@ +package depromeet.onepiece.feedback.query.presentation; + +/** + * 도메인 간 의존성을 제거하고 이벤트 소싱을 적용하기 위해 CQRS 패턴을 적용해요. CQRS 패턴은 명령(Command)과 조회(Query)를 분리하는 패턴으로, 이 중에서 + * 조회(Query)는 상태를 조회하는 역할을 담당해요. Master-Slave 구조로 데이터베이스가 구성되어 있을 때, 조회 모델을 따로 분리하는 것이 조회 성능 향상에 도움이 + * 될 수 있어요. + */ +public class FeedbackQueryController {} diff --git a/src/main/java/depromeet/onepiece/feedback/query/presentation/request/.gitkeep b/src/main/java/depromeet/onepiece/feedback/query/presentation/request/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/depromeet/onepiece/feedback/query/presentation/response/.gitkeep b/src/main/java/depromeet/onepiece/feedback/query/presentation/response/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/depromeet/onepiece/portfolio/domain/exception/PortfolioDomainExceptionCode.java b/src/main/java/depromeet/onepiece/portfolio/domain/exception/PortfolioDomainExceptionCode.java new file mode 100644 index 0000000..70f3640 --- /dev/null +++ b/src/main/java/depromeet/onepiece/portfolio/domain/exception/PortfolioDomainExceptionCode.java @@ -0,0 +1,3 @@ +package depromeet.onepiece.portfolio.domain.exception; + +public enum PortfolioDomainExceptionCode {} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8664a57..a5c8409 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,4 +13,9 @@ springdoc: default-produces-media-type: application/json swagger-ui: operations-sorter: alpha - tags-sorter: alpha \ No newline at end of file + tags-sorter: alpha +chatgpt: + api-key: ${CHATGPT_API_KEY} + model: "gpt-4-mini" + temperature: 0.7 + maxTokens: 4096