From bf3d95ff7cc82757bc3db2ff8334e9e496e03324 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Tue, 26 May 2026 14:41:44 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Chore]=20active=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d2c0e0a..a5022df 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,3 +1,4 @@ spring: profiles: - active: ${SPRING_PROFILES_ACTIVE:dev} + active: ${SPRING_PROFILES_ACTIVE:prod} + From 0e7d927a1d71e1cfa873990ac2d23a44a59695ba Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Wed, 27 May 2026 09:21:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Refactor]=20sequence=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnalysisController.java | 6 +- .../analysis/service/AnalysisService.java | 40 ++++++++++++- .../repository/MockApplyRepository.java | 1 + .../analysis/service/AnalysisServiceTest.java | 56 +++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java index d5e552d..71874c9 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -40,11 +41,12 @@ public ApiResponse analyze( @GetMapping public ApiResponse getAnalysis( @AuthenticationPrincipal UserDetailsImpl userDetails, - @PathVariable Long mockApplyId + @PathVariable Long mockApplyId, + @RequestParam(required = false) Integer sequence ) { return ApiResponse.onSuccess( "자소서 분석 결과 조회에 성공했습니다.", - analysisService.getAnalysis(getAuthenticatedUser(userDetails), mockApplyId) + analysisService.getAnalysis(getAuthenticatedUser(userDetails), mockApplyId, sequence) ); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index abd654b..41753aa 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -88,19 +88,53 @@ public AnalysisResponse analyze(User user, Long mockApplyId) { } public AnalysisResponse getAnalysis(User user, Long mockApplyId) { + return getAnalysis(user, mockApplyId, null); + } + + public AnalysisResponse getAnalysis(User user, Long mockApplyId, Integer sequence) { MockApply mockApply = getOwnedMockApply(user, mockApplyId); - Analysis analysis = analysisRepository.findByMockApplyId(mockApply.getId()) + if (sequence != null) { + mockApply = resolveMockApplyBySequence(mockApply, sequence); + } + Long resolvedMockApplyId = mockApply.getId(); + + Analysis analysis = analysisRepository.findByMockApplyId(resolvedMockApplyId) .orElseThrow(() -> new GeneralException( GeneralErrorCode.ANALYSIS_NOT_FOUND, - "해당 모의 서류 지원의 분석 결과를 찾을 수 없습니다. mockApplyId=" + mockApplyId + "해당 모의 서류 지원의 분석 결과를 찾을 수 없습니다. mockApplyId=" + resolvedMockApplyId )); - List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(mockApply.getId()); + List questions = questionRepository.findAllByMockApplyIdOrderByIdAsc(resolvedMockApplyId); List questionAnalyses = questionAnalysisRepository.findAllByAnalysisIdOrderByQuestionIdAscIdAsc(analysis.getId()); return toResponse(mockApply, analysis, questions, questionAnalyses); } + private MockApply resolveMockApplyBySequence(MockApply baseMockApply, int sequence) { + if (sequence < 1) { + throw new GeneralException( + GeneralErrorCode.INVALID_PARAMETER, + "sequence는 1 이상의 값이어야 합니다." + ); + } + + List mockApplies = mockApplyRepository.findAllByUserIdAndJobPostingIdOrderByIdAsc( + baseMockApply.getUser().getId(), + baseMockApply.getJobPosting().getId() + ); + + return mockApplies.stream() + .filter(mockApply -> sequence == mockApplyRepository.calculateSequence(mockApply)) + .findFirst() + .orElseThrow(() -> new GeneralException( + GeneralErrorCode.MOCK_APPLY_NOT_FOUND, + "해당 순번의 모의 서류 지원을 찾을 수 없습니다. mockApplyId=" + + baseMockApply.getId() + + ", sequence=" + + sequence + )); + } + private void replaceExistingAnalysis(MockApply mockApply) { Optional existingAnalysis = analysisRepository.findByMockApplyId(mockApply.getId()); if (existingAnalysis.isEmpty()) { diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java index 3b89f1e..48b7183 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java @@ -11,6 +11,7 @@ public interface MockApplyRepository extends JpaRepository { List findAllByUserId(Long userId); List findAllByJobPostingId(Long jobPostingId); + List findAllByUserIdAndJobPostingIdOrderByIdAsc(Long userId, Long jobPostingId); long countByUserIdAndJobPostingId(Long userId, Long jobPostingId); @Query(""" diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java index bff8e14..bbbcbcb 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java @@ -463,6 +463,62 @@ void getAnalysis() { assertThat(response.questions().get(0).analyses()).hasSize(1); } + @Test + @DisplayName("sequence 쿼리값으로 같은 공고의 특정 회차 분석 결과를 조회한다") + void getAnalysisBySequence() { + User user = saveUser("analysis-get-sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user); + MockApply firstMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + MockApply secondMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + saveQuestion(firstMockApply, "첫 번째 지원 동기", "첫 번째 답변입니다."); + Question secondQuestion = saveQuestion(secondMockApply, "두 번째 지원 동기", "두 번째 답변입니다."); + when(analysisAiClient.analyze(any(), any())) + .thenReturn(new AnalysisLlmResponse( + 61, + 62, + 63, + 64, + "첫 번째 분석 결과", + List.of() + )) + .thenReturn(new AnalysisLlmResponse( + 81, + 82, + 83, + 84, + "두 번째 분석 결과", + List.of(new AnalysisLlmResponse.QuestionAnalysisItem( + secondQuestion.getId(), + "두 번째 답변입니다.", + "mentioned", + "근거가 더 필요합니다.", + "두 번째 답변에 구체적인 성과를 추가했습니다." + )) + )); + + analysisService.analyze(user, firstMockApply.getId()); + AnalysisResponse saved = analysisService.analyze(user, secondMockApply.getId()); + + AnalysisResponse response = analysisService.getAnalysis(user, firstMockApply.getId(), 2); + + assertThat(response.analysisId()).isEqualTo(saved.analysisId()); + assertThat(response.mockApplyId()).isEqualTo(secondMockApply.getId()); + assertThat(response.sequence()).isEqualTo(2); + assertThat(response.feedback()).isEqualTo("두 번째 분석 결과"); + } + + @Test + @DisplayName("존재하지 않는 sequence로 분석 결과 조회 시 예외를 던진다") + void getAnalysisThrowsWhenSequenceDoesNotExist() { + User user = saveUser("analysis-get-sequence-missing@example.com"); + MockApply mockApply = saveMockApply(user); + + assertThatThrownBy(() -> analysisService.getAnalysis(user, mockApply.getId(), 2)) + .isInstanceOf(GeneralException.class) + .extracting("code") + .isEqualTo(GeneralErrorCode.MOCK_APPLY_NOT_FOUND); + } + @Test @DisplayName("크레딧 1개인 사용자가 동시에 분석을 요청해도 하나만 성공한다") void analyzeConcurrentlyUsesCreditOnlyOnce() throws Exception { From b2ee995973904e6095eea639f6a206b3e5408ff2 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Wed, 27 May 2026 09:29:20 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Refactor]=20N+1=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analysis/service/AnalysisService.java | 29 +++++++----- src/main/resources/application.yaml | 3 +- .../analysis/service/AnalysisServiceTest.java | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index 41753aa..f1ef976 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -123,16 +123,25 @@ private MockApply resolveMockApplyBySequence(MockApply baseMockApply, int sequen baseMockApply.getJobPosting().getId() ); - return mockApplies.stream() - .filter(mockApply -> sequence == mockApplyRepository.calculateSequence(mockApply)) - .findFirst() - .orElseThrow(() -> new GeneralException( - GeneralErrorCode.MOCK_APPLY_NOT_FOUND, - "해당 순번의 모의 서류 지원을 찾을 수 없습니다. mockApplyId=" - + baseMockApply.getId() - + ", sequence=" - + sequence - )); + int derivedSequence = 0; + for (MockApply mockApply : mockApplies) { + derivedSequence++; + int resolvedSequence = mockApply.getSequence() != null && mockApply.getSequence() > 0 + ? mockApply.getSequence() + : derivedSequence; + + if (resolvedSequence == sequence) { + return mockApply; + } + } + + throw new GeneralException( + GeneralErrorCode.MOCK_APPLY_NOT_FOUND, + "해당 순번의 모의 서류 지원을 찾을 수 없습니다. mockApplyId=" + + baseMockApply.getId() + + ", sequence=" + + sequence + ); } private void replaceExistingAnalysis(MockApply mockApply) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a5022df..d2c0e0a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,4 +1,3 @@ spring: profiles: - active: ${SPRING_PROFILES_ACTIVE:prod} - + active: ${SPRING_PROFILES_ACTIVE:dev} diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java index bbbcbcb..d014a49 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java @@ -507,6 +507,50 @@ void getAnalysisBySequence() { assertThat(response.feedback()).isEqualTo("두 번째 분석 결과"); } + @Test + @DisplayName("sequence 쿼리값 조회는 저장된 지원 순번을 우선 사용한다") + void getAnalysisByStoredSequence() { + User user = saveUser("analysis-get-stored-sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user); + MockApply firstMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + MockApply secondMockApply = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL, 4)); + saveQuestion(firstMockApply, "첫 번째 지원 동기", "첫 번째 답변입니다."); + Question secondQuestion = saveQuestion(secondMockApply, "두 번째 지원 동기", "두 번째 답변입니다."); + when(analysisAiClient.analyze(any(), any())) + .thenReturn(new AnalysisLlmResponse( + 61, + 62, + 63, + 64, + "첫 번째 분석 결과", + List.of() + )) + .thenReturn(new AnalysisLlmResponse( + 81, + 82, + 83, + 84, + "저장 순번 분석 결과", + List.of(new AnalysisLlmResponse.QuestionAnalysisItem( + secondQuestion.getId(), + "두 번째 답변입니다.", + "mentioned", + "근거가 더 필요합니다.", + "두 번째 답변에 구체적인 성과를 추가했습니다." + )) + )); + + analysisService.analyze(user, firstMockApply.getId()); + AnalysisResponse saved = analysisService.analyze(user, secondMockApply.getId()); + + AnalysisResponse response = analysisService.getAnalysis(user, firstMockApply.getId(), 4); + + assertThat(response.analysisId()).isEqualTo(saved.analysisId()); + assertThat(response.mockApplyId()).isEqualTo(secondMockApply.getId()); + assertThat(response.sequence()).isEqualTo(4); + assertThat(response.feedback()).isEqualTo("저장 순번 분석 결과"); + } + @Test @DisplayName("존재하지 않는 sequence로 분석 결과 조회 시 예외를 던진다") void getAnalysisThrowsWhenSequenceDoesNotExist() {