Skip to content

Conversation

@kgy1008
Copy link
Member

@kgy1008 kgy1008 commented Jul 27, 2025

📣 Jira Ticket

EDMT-293

👩‍💻 작업 내용

필요한 엔티티들을 추가로 구현했습니다 -> ERD Cloud 구조 기반
기존에는 응답1,2,3이 모두 생성되어야 한꺼번에 반환하는 구조였다면 스트리밍으로 사용해서 반환하도록 수정했습니다.
외부 연동인 만큼 연결/읽기 타임아웃을 설정했습니다.


Security Filter 단에 오류가 있어서 이 부분도 고치고, Resolver를 등록하는 설정도 빠져있어 해당 PR에 한꺼번에 반영했습니다.
-> 이거 때문에 이번에도 파일 변경이 많네요ㅠㅠ

📝 리뷰 요청 & 논의하고 싶은 내용

스트리밍 구조

저희는 한번의 요청으로 3가지의 응답을 반환하는 구조를 지닙니다. 때문에 한글자씩 스트리밍을 진행할 경우, 응답 사이를 구분하기 위해 구분자를 넣어야 했는데, 구분자 자체도 스트리밍되어 분리되어 나가더라구요... 이렇게 되면 프론트엔드에서는 이를 버퍼링 후 파싱하는 방식으로 처리해야 하는데, 구분자 파싱에 실패할 가능성도 있어 안전하지 않다고 생각했습니다.

그래서 현재는 서버 측에서 각 응답을 버퍼링한 뒤, 완성된 응답 단위로 스트리밍하는 방식으로 구현했습니다. 첫 응답이 아무리 늦어도 약 7초 이내에 도착해, 사용자 체감 속도는 확실히 개선될 것 같습니다.

즉, 기존에는 (응답1, 응답2, 응답3 모두 생성) -> 전체 반환 구조 였다면 현재는 (응답 1 생성) -> 즉시 응답1 반환, (응답 2 생성) -> 즉시 응답2 반환... 구조입니다. 테스트를 해봤는데 응답 속도도 첫번째 응답이 아무리 늦어도 7초 이내로 도착합니다.

타임아웃 설정

Spring AI 프레임워크 자체에서는 타임아웃을 설정할 수 있는 방법이 없어서, 내부적으로 사용하는 WebClient를 커스터마이징했습니다.
처음에는 Spring AI가 초기화될 때 커스터마이징된 WebClient를 주입하는 방식으로 구성했는데, OpenAI 호출 시점에 해당 객체가 주입되지 않는 문제가 있어, 현재는 WebClientCustomizer를 통해 전역적으로 타임아웃을 설정했습니다.
-> 이것도 전역적으로 처리 되지 않게끔 하는 방법을 좀 찾아보겠습니다.

권한 설정

정확한 원인은 파악하지 못했지만, 스트리밍 방식으로 응답을 반환할 때 요청 자체는 정상적으로 처리되었음에도, 모든 응답이 끝나고 나서 Spring Security 필터가 다시 권한 검사를 시도하면서 권한 오류가 발생했습니다. 열심히 해결해보려고 했는데,,,, 도저히 방법을 찾을 수 없어서 일단은 Spring Security 필터에서는 인가 필터는 무조건 통과시키고, 메서드 내부에서 직접 권한을 검사하도록 처리해두었습니다.

Controller, Facade 분리

기존에는 StudentRecordController에 CRUD 로직들이랑 같이 다 섞여 있었는데, 이게 저희 서비스의 핵심 기능인 만큼 단순 CRUD 작업들이랑 분리될 필요성이 있다고 생각하여, Controller와 Facade를 해당 기능 전용으로 분리해두었습니다. 후에 비동기처리 같은 것이 들어가면 코드가 더 많아질 것 같아요.

이 외..

함수형 프로그래밍 문법이랑 WebFlux에 대한 지식이 많이 부족해서 커서의 힘을 많이 빌렸습니다... 예외 처리 같은 것도 현재는 많이 빈약한데 후에 재시도 로직과 서킷 브레이커 적용 시키면서 차근차근 보완해나가도록 하겠습니다.


이전 버전의 API 경로나 함수들은 혹시 모르니, 지우지 않고 주석 처리로 남겨두었습니다.

📸 스크린 샷 (선택)

image

Summary by CodeRabbit

  • 신규 기능

    • 학생부 AI 생성 기능이 추가되어, 학생부 기록을 AI로 스트리밍 생성할 수 있는 엔드포인트가 제공됩니다.
    • 학생부 AI 태스크 및 결과 관리, 버전별 AI 생성 결과 스트리밍 기능이 도입되었습니다.
    • 학생부 AI 프롬프트 생성 유틸리티와 관련 데이터 구조가 추가되었습니다.
    • 학생부 CRUD 및 AI 전용 컨트롤러가 분리되어 생성되었습니다.
  • 버그 수정/개선

    • 학생부 및 멤버 접근 권한 체크가 강화되었습니다.
    • 일부 에러 메시지 및 열(column) 명칭이 명확하게 수정되었습니다.
  • 보안/인증

    • 학생부 관련 엔드포인트의 접근 권한 정책이 개선되어, AI 생성 엔드포인트는 전체 허용, 나머지는 역할 기반으로 제한됩니다.
    • 미인증 교사에 대한 안내 메시지가 추가되었습니다.
  • 구성 및 설정

    • OpenAI 연동 설정 및 WebClient 커스터마이징이 개선되었습니다.
    • 불필요한 파일(기존 OpenAI 연동, 설정 등) 삭제 및 신규 설정 구조로 전환되었습니다.
  • 문서 및 스타일

    • 유효성 검증 메시지 등 사용자 안내 메시지가 한글로 추가되었습니다.

kgy1008 added 19 commits July 27, 2025 18:32
@kgy1008 kgy1008 requested a review from TaegeunYou July 27, 2025 17:45
@kgy1008 kgy1008 self-assigned this Jul 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 27, 2025

"""

Walkthrough

AI 기반 학생부 생성 및 관리 기능을 위한 대규모 신규 도메인과 API가 추가되었습니다. AI 프롬프트 생성, AI 태스크 및 결과 엔티티, OpenAI 연동 서비스, WebFlux 기반 SSE 스트리밍 컨트롤러, 권한 검증 및 예외 코드, Spring AI/WebFlux 설정 등 전반적으로 신규 파일과 클래스가 다수 도입되었습니다.

Changes

파일 Cohort 변경 요약
build.gradle 모든 서브프로젝트에 spring-boot-starter-webflux 의존성 추가
edukit-api/.../WebMvcConfig.java
edukit-api/.../resolver/MemberIdArgumentResolver.java
WebMvc 설정 신규 추가 및 ArgumentResolver 타입 제한, 시그니처 변경
edukit-api/.../controller/studentrecord/StudentRecordAIController.java
edukit-api/.../controller/studentrecord/StudentRecordController.java
edukit-api/.../controller/studentrecord/request/StudentRecordPromptRequest.java
학생부 AI 생성 스트리밍 API 및 CRUD 컨트롤러 신규 도입, 프롬프트 요청 DTO 추가
edukit-api/.../security/authentication/MemberDetailReader.java User 객체 생성 시 패스워드 값을 null에서 "unused"로 변경
edukit-api/.../security/config/SecurityConfig.java 학생부 AI 생성 API 경로에 대한 permitAll 추가 및 학생부 관련 경로 패턴 확장
edukit-core/.../auth/exception/AuthErrorCode.java FORBIDDEN_MEMBER 메시지에 교사 인증 안내 문구 추가
edukit-core/.../auth/facade/AuthFacade.java PENDING_TEACHER 권한 체크 및 예외 발생 메서드 추가
edukit-core/.../member/exception/MemberErrorCode.java enum trailing comma 제거(문법적 정리)
edukit-core/.../member/repository/MemberRepository.java memberId 및 삭제 여부로 조회하는 신규 메서드 추가
edukit-core/.../member/service/MemberService.java memberId로 Member 조회하는 신규 메서드 추가
edukit-core/.../studentrecord/entity/StudentRecord.java PK 컬럼명 student_record_detail_id → student_record_id로 변경
edukit-core/.../studentrecord/entity/StudentRecordAIResult.java
edukit-core/.../studentrecord/entity/StudentRecordAITask.java
edukit-core/.../studentrecord/enums/AITaskStatus.java
AI 결과/태스크 엔티티 및 태스크 상태 enum 신규 도입
edukit-core/.../studentrecord/exception/StudentRecordErrorCode.java
edukit-core/.../studentrecord/exception/StudentRecordException.java
학생부 관련 에러코드 enum 및 예외 클래스 신설, 생성자 타입 명확화
edukit-core/.../studentrecord/facade/StudentRecordAIFacade.java
edukit-core/.../studentrecord/facade/StudentRecordFacade.java
학생부 AI/일반 비즈니스 파사드 신규 도입, AI 프롬프트 및 스트리밍 응답 처리
edukit-core/.../studentrecord/facade/response/StudentRecordCreateResponse.java
edukit-core/.../studentrecord/facade/response/StudentRecordTaskResponse.java
학생부 생성/태스크 응답 record 및 팩토리 메서드 추가
edukit-core/.../studentrecord/repository/StudentRecordAITaskRepository.java
edukit-core/.../studentrecord/repository/StudentRecordRepository.java
학생부 및 AI 태스크 JPA 리포지토리 신규 생성
edukit-core/.../studentrecord/service/StudentRecordService.java 학생부 상세 조회 및 AI 태스크 생성 서비스 신규 도입
edukit-core/.../studentrecord/util/AIPromptGenerator.java 학생부 유형별 AI 프롬프트 생성 유틸리티 신규 추가
edukit-external/.../ai/OpenAIService.java
edukit-external/.../ai/response/OpenAIVersionResponse.java
OpenAI 스트리밍 응답 서비스 및 버전별 응답 record 신설
edukit-external/.../ai/setting/OpenAIProperties.java
edukit-external/.../ai/setting/WebClientConfig.java
OpenAI 설정 record 및 WebClient 커스터마이저 신규 도입
edukit-external/.../config/SpringAIConfig.java ChatClient 빈 설정 신규 도입
edukit-external/.../ai/OpenAiClient.java
edukit-external/.../ai/OpenAiProperties.java
edukit-external/.../ai/dto/response/StudentRecordAICreateResponse.java
edukit-external/.../config/SpringAiConfig.java
기존 OpenAI 클라이언트, 설정, DTO, Spring AI 설정 파일 삭제

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StudentRecordAIController
    participant AuthFacade
    participant StudentRecordAIFacade
    participant StudentRecordService
    participant OpenAIService

    Client->>StudentRecordAIController: POST /api/v2/student-records/ai-generate/{recordId}/stream
    StudentRecordAIController->>AuthFacade: checkHasPermission(memberId)
    AuthFacade-->>StudentRecordAIController: (권한 검증)
    StudentRecordAIController->>StudentRecordAIFacade: getStreamingPrompt(memberId, recordId, byteCount, prompt)
    StudentRecordAIFacade->>StudentRecordService: getRecordDetail(memberId, recordId)
    StudentRecordService-->>StudentRecordAIFacade: StudentRecord
    StudentRecordAIFacade->>StudentRecordService: createAITask(studentRecord, prompt)
    StudentRecordService-->>StudentRecordAIFacade: taskId
    StudentRecordAIFacade-->>StudentRecordAIController: StudentRecordTaskResponse
    StudentRecordAIController->>StudentRecordAIFacade: generateAIStudentRecordStream(prompt)
    StudentRecordAIFacade->>OpenAIService: getVersionedStreamingResponse(prompt)
    OpenAIService-->>StudentRecordAIFacade: Flux<OpenAIVersionResponse>
    StudentRecordAIFacade-->>StudentRecordAIController: Flux<StudentRecordCreateResponse>
    StudentRecordAIController-->>Client: SSE stream (ServerSentEvent<StudentRecordCreateResponse>)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

feat

Suggested reviewers

  • TaegeunYou

Poem

🐇
새싹 돋는 학생부에 AI 바람 솔솔~
프롬프트 짓고, 태스크 쌓고, 스트림 따라 흘러가네
교사 인증 꼼꼼히, 권한 체크도 완벽히!
WebFlux로 흘러가는 SSE,
OpenAI와 손잡고 미래로 hop hop!
코드밭에 변화의 싹, 토끼가 신나게 뛰어놀아요 🥕
"""

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/EDMT-293

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (17)
build.gradle (1)

46-46: WebFlux 의존성 추가 범위 검토 필요

모든 서브프로젝트에 WebFlux 의존성을 추가하는 것이 적절한지 검토가 필요합니다. 스트리밍 AI 응답 기능이 특정 모듈(예: edukit-api)에서만 사용된다면, 해당 모듈의 build.gradle에만 의존성을 추가하는 것이 더 효율적일 수 있습니다.

-		implementation 'org.springframework.boot:spring-boot-starter-webflux'

대신 필요한 모듈에서만 개별적으로 추가:

// edukit-api/build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
edukit-core/src/main/java/com/edukit/core/studentrecord/repository/StudentRecordRepository.java (1)

1-7: 가시성 향상을 위해 @repository 애노테이션 추가 고려

Spring Data JPA가 자동으로 빈을 등록하므로 기능상 문제는 없지만, 팀 내 일관성과 IDE 검색 편의성을 위해 명시적으로 @Repository를 부여하는 것을 권장드립니다.

 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
 
-public interface StudentRecordRepository extends JpaRepository<StudentRecord, Long> {
+@Repository
+public interface StudentRecordRepository extends JpaRepository<StudentRecord, Long> {
 }
edukit-core/src/main/java/com/edukit/core/studentrecord/repository/StudentRecordAITaskRepository.java (1)

1-7: 명시적 애노테이션 추가 제안

앞선 StudentRecordRepository와 동일하게, 가독성 확보 차원에서 @Repository 애노테이션을 붙여두면 좋습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/facade/response/StudentRecordTaskResponse.java (2)

7-9: 정적 팩토리 메소드가 불필요합니다.

Record 클래스는 이미 public 생성자를 제공하므로 of 메소드가 추가적인 가치를 제공하지 않습니다. 단순히 생성자를 래핑하는 것보다는 생성자를 직접 사용하는 것이 더 간단합니다.

-    public static StudentRecordTaskResponse of(final long taskId, final String inputPrompt) {
-        return new StudentRecordTaskResponse(taskId, inputPrompt);
-    }

3-6: 입력 매개변수 검증 고려사항

taskId가 양수여야 하고 inputPrompt가 null이 아니어야 하는 경우, compact constructor에서 검증을 추가하는 것을 고려해보세요.

 public record StudentRecordTaskResponse(
         long taskId,
         String inputPrompt
 ) {
+    public StudentRecordTaskResponse {
+        if (taskId <= 0) {
+            throw new IllegalArgumentException("taskId는 양수여야 합니다");
+        }
+        if (inputPrompt == null || inputPrompt.isBlank()) {
+            throw new IllegalArgumentException("inputPrompt는 비어있을 수 없습니다");
+        }
+    }
edukit-external/src/main/java/com/edukit/external/ai/response/OpenAIVersionResponse.java (2)

9-11: 정적 팩토리 메소드가 불필요합니다.

Record 생성자가 이미 public이므로 of 메소드는 추가적인 가치를 제공하지 않습니다.

-    public static OpenAIVersionResponse of(final int versionNumber, final String content, final boolean isLast) {
-        return new OpenAIVersionResponse(versionNumber, content, isLast);
-    }

3-7: 입력 검증 추가를 고려해보세요.

AI 응답의 무결성을 보장하기 위해 compact constructor에서 기본적인 검증을 추가하는 것을 권장합니다.

 public record OpenAIVersionResponse(
         int versionNumber,
         String content,
         boolean isLast
 ) {
+    public OpenAIVersionResponse {
+        if (versionNumber < 1) {
+            throw new IllegalArgumentException("versionNumber는 1 이상이어야 합니다");
+        }
+        if (content == null) {
+            throw new IllegalArgumentException("content는 null일 수 없습니다");
+        }
+    }
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/response/StudentRecordCreateResponse.java (1)

8-10: 정적 팩토리 메소드가 불필요합니다.

Record 생성자를 직접 사용하는 것이 더 간단합니다.

-    public static StudentRecordCreateResponse of(final int versionNumber, final String content, final boolean isLast) {
-        return new StudentRecordCreateResponse(versionNumber, content, isLast);
-    }
edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAIResult.java (2)

33-34: result 필드에 길이 제약 조건 추가를 고려하세요.

AI 결과 텍스트의 길이가 매우 클 수 있으므로 @Column 어노테이션에 적절한 길이 제약이나 @Lob 어노테이션 사용을 고려해보세요.

-    @Column(nullable = false)
+    @Column(nullable = false, length = 10000) // 또는 @Lob 사용
     private String result;

36-36: JPA Auditing 어노테이션 사용을 고려하세요.

일관성을 위해 @CreationTimestamp 어노테이션 사용을 고려해보세요.

+    @CreationTimestamp
     private LocalDateTime createdAt;
edukit-external/src/main/java/com/edukit/external/ai/setting/WebClientConfig.java (2)

25-32: 타임아웃 설정에 로깅 추가를 고려하세요.

타임아웃 값이 제대로 적용되었는지 확인할 수 있도록 디버그 로깅을 추가하는 것이 좋습니다.

         return webClientBuilder -> {
+            log.debug("Configuring OpenAI WebClient with connection timeout: {}ms, read timeout: {}ms", 
+                     properties.connectionTimeout(), properties.readTimeout());
             HttpClient httpClient = HttpClient.create()

37-37: User-Agent 값을 설정으로 외부화하는 것을 고려하세요.

하드코딩된 User-Agent 값을 properties로 외부화하면 더 유연해집니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAITask.java (1)

41-45: 상태 전환 및 완료 시간 설정을 위한 메서드 추가를 고려해보세요.

현재 엔티티에는 생성을 위한 팩토리 메서드만 있고, 상태 변경이나 완료 시간 설정을 위한 메서드가 없습니다. 도메인 로직의 캡슐화를 위해 상태 전환 메서드들을 추가하는 것을 권장합니다.

다음과 같은 메서드들을 추가할 수 있습니다:

+    public void markInProgress() {
+        this.status = AITaskStatus.IN_PROGRESS;
+    }
+
+    public void markCompleted() {
+        this.status = AITaskStatus.COMPLETED;
+        this.completedAt = LocalDateTime.now();
+    }
+
+    public void markFailed() {
+        this.status = AITaskStatus.FAILED;
+        this.completedAt = LocalDateTime.now();
+    }
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (1)

24-33: MemberService 사용이 불필요해 보입니다.

getStreamingPrompt 메서드에서 MemberService를 주입받았지만 실제로는 사용하지 않고 있습니다. studentRecordService.getRecordDetail(memberId, recordId)에서 이미 권한 검증을 수행하므로 중복된 의존성일 수 있습니다.

만약 멤버 존재 여부 검증이 필요 없다면 MemberService 의존성을 제거할 수 있습니다:

-    private final MemberService memberService;
edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordAIController.java (1)

59-62: 에러 메시지 국제화를 고려해보세요.

현재 에러 메시지가 한국어로 하드코딩되어 있습니다. API 응답의 일관성과 국제화를 위해 에러 코드나 메시지 상수를 사용하는 것을 권장합니다.

다음과 같이 개선할 수 있습니다:

+    private static final String STREAMING_ERROR_MESSAGE = "스트리밍 중 오류가 발생했습니다";
+
                 .onErrorResume(throwable -> Flux.just(ServerSentEvent.<StudentRecordCreateResponse>builder()
                         .event("error")
-                        .comment("스트리밍 중 오류가 발생했습니다: " + throwable.getMessage())
+                        .comment(STREAMING_ERROR_MESSAGE + ": " + throwable.getMessage())
                         .build()));
edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (2)

22-26: 대용량 응답에 대한 메모리 관리 고려 필요

StringBuilder가 제한 없이 증가할 수 있어 매우 큰 AI 응답의 경우 메모리 문제가 발생할 수 있습니다. 최대 버퍼 크기 제한을 고려해보세요.

 public Flux<OpenAIVersionResponse> getVersionedStreamingResponse(final String prompt) {
+    final int MAX_BUFFER_SIZE = 100_000; // 100KB 제한
     return Flux.create(sink -> {
         StringBuilder buffer = new StringBuilder();
         AtomicInteger currentVersion = new AtomicInteger(0);

52-73: 버전 개수가 하드코딩되어 있음

현재 구현은 정확히 3개의 버전을 가정하고 있습니다. 향후 확장성을 위해 버전 개수를 설정 가능하게 만드는 것을 고려해보세요.

+private static final int TOTAL_VERSIONS = 3;

 () -> {
     // 스트림 완료 시 마지막 버전 처리
     String finalBuffer = buffer.toString();
     
     // 아직 처리하지 않은 버전이 있다면 처리
-    if (currentVersion.get() < 3) {
-        String version3Content = extractCompleteVersion(finalBuffer, 3);
+    if (currentVersion.get() < TOTAL_VERSIONS) {
+        for (int v = currentVersion.get() + 1; v <= TOTAL_VERSIONS; v++) {
+            String versionContent = extractCompleteVersion(finalBuffer, v);
             
-        if (!version3Content.isEmpty()) {
-            sink.next(OpenAIVersionResponse.of(
-                    3,
-                    version3Content,
-                    true // 마지막 버전
-            ));
+            if (!versionContent.isEmpty()) {
+                sink.next(OpenAIVersionResponse.of(
+                        v,
+                        versionContent,
+                        v == TOTAL_VERSIONS // 마지막 버전
+                ));
+            }
         }
     }
     
     sink.complete();
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79785a4 and 78e2185.

📒 Files selected for processing (35)
  • build.gradle (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/common/config/WebMvcConfig.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/common/resolver/MemberIdArgumentResolver.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordAIController.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordController.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/controller/studentrecord/request/StudentRecordPromptRequest.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/security/authentication/MemberDetailReader.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/security/config/SecurityConfig.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/auth/exception/AuthErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/auth/facade/AuthFacade.java (2 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/service/MemberService.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecord.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAIResult.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAITask.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/enums/AITaskStatus.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/exception/StudentRecordErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/exception/StudentRecordException.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordFacade.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/response/StudentRecordCreateResponse.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/response/StudentRecordTaskResponse.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/repository/StudentRecordAITaskRepository.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/repository/StudentRecordRepository.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordService.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/util/AIPromptGenerator.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAiClient.java (0 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAiProperties.java (0 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/dto/response/StudentRecordAICreateResponse.java (0 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/response/OpenAIVersionResponse.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/setting/OpenAIProperties.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/setting/WebClientConfig.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/config/SpringAIConfig.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/config/SpringAiConfig.java (0 hunks)
💤 Files with no reviewable changes (4)
  • edukit-external/src/main/java/com/edukit/external/ai/dto/response/StudentRecordAICreateResponse.java
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAiProperties.java
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAiClient.java
  • edukit-external/src/main/java/com/edukit/external/config/SpringAiConfig.java
🧰 Additional context used
🧬 Code Graph Analysis (5)
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordFacade.java (2)
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (1)
  • Service (16-63)
edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordService.java (1)
  • Service (14-45)
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (3)
edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (1)
  • Service (10-125)
edukit-core/src/main/java/com/edukit/core/member/service/MemberService.java (1)
  • Service (16-69)
edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordService.java (1)
  • Service (14-45)
edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordAIController.java (1)
edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordController.java (1)
  • RestController (7-13)
edukit-core/src/main/java/com/edukit/core/auth/facade/AuthFacade.java (1)
edukit-core/src/main/java/com/edukit/core/auth/exception/AuthException.java (1)
  • AuthException (6-15)
edukit-external/src/main/java/com/edukit/external/ai/setting/OpenAIProperties.java (1)
edukit-external/src/main/java/com/edukit/external/ai/OpenAiProperties.java (1)
  • OpenAiProperties (5-12)
🔇 Additional comments (26)
edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1)

13-13: 구문 오류 수정 완료

enum 상수의 trailing comma 제거로 올바른 Java 구문이 되었습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecord.java (1)

27-28: PK 컬럼명 변경에 따른 마이그레이션 확인 필요

기존 "student_record_detail_id""student_record_id"로 컬럼명이 변경되었습니다.
운영 DB에 이미 테이블이 존재한다면:

  1. DDL 마이그레이션 스크립트가 준비-적용되었는지,
  2. 네이티브 SQL을 직접 사용하던 코드·뷰·프로시저에 하드코딩된 컬럼명이 없는지

반드시 확인해 주세요. 컬럼 불일치 시 애플리케이션 부팅 오류나 런타임 쿼리 실패가 발생할 수 있습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/enums/AITaskStatus.java (1)

1-8: 새 enum 정의 문제없음

도메인 의미가 명확하며 추가 로직도 필요 없습니다.
단, DB에 저장될 경우 @Enumerated(EnumType.STRING)을 사용하도록 엔티티 필드 쪽에 명시되어 있는지 확인만 하면 충분합니다.

edukit-core/src/main/java/com/edukit/core/auth/exception/AuthErrorCode.java (1)

14-15: 변경된 메시지로 인한 사이드이펙트 점검

에러 메시지가 변경되면서 기존 테스트 코드나 프론트엔드에서 문자열 매칭으로 검증·표시하던 부분이 실패할 수 있습니다.
관련 스펙·테스트를 업데이트했는지 확인 바랍니다.

edukit-core/src/main/java/com/edukit/core/auth/facade/AuthFacade.java (2)

5-6: 새로운 예외 및 역할 열거형 임포트가 적절하게 추가됨

새로 추가된 AuthErrorCode, AuthException, MemberRole 임포트들이 새로운 권한 검증 메서드를 지원하기 위해 적절하게 추가되었습니다.

Also applies to: 13-13


52-58: 권한 검증 로직이 올바르게 구현됨

새로 추가된 checkHasPermission 메서드가 다음과 같이 잘 구현되었습니다:

  • @Transactional(readOnly = true) 어노테이션으로 읽기 전용 트랜잭션 설정
  • PENDING_TEACHER 역할에 대한 접근 제한 로직
  • 적절한 예외 처리

PR 목표에서 언급된 Spring Security 필터 우회와 함께 메서드 내부에서 명시적 권한 검증을 수행하는 방식이 합리적입니다.

edukit-api/src/main/java/com/edukit/api/security/config/SecurityConfig.java (1)

50-50: API 버전 패턴 일반화가 적절함

학생부 관련 엔드포인트에 대한 패턴을 /api/v*/student-records/**로 일반화한 것이 적절합니다. 이를 통해 기존 v1 API와 새로운 v2 API 모두를 지원할 수 있습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/exception/StudentRecordException.java (1)

7-9: 타입 안전성 개선이 잘 적용됨

생성자 파라미터를 일반적인 ErrorCode에서 도메인 특화된 StudentRecordErrorCode로 변경한 것이 좋은 개선입니다. 이를 통해:

  • 타입 안전성 향상
  • 도메인별 에러 코드 명확화
  • 컴파일 타임 오류 검증 강화

도메인 특화 예외 처리 패턴을 잘 따르고 있습니다.

edukit-api/src/main/java/com/edukit/api/common/config/WebMvcConfig.java (1)

10-20: 깔끔한 Spring MVC 설정입니다.

표준적인 Spring MVC 설정 패턴을 잘 따르고 있으며, MemberIdArgumentResolver를 적절히 등록하고 있습니다. 코드가 명확하고 간결합니다.

edukit-external/src/main/java/com/edukit/external/config/SpringAIConfig.java (1)

9-17: 간결하고 명확한 Spring AI 설정입니다.

ChatClient 빈 설정이 깔끔하게 구현되어 있습니다. 빌더 패턴을 적절히 사용하고 있으며, 이전 복잡한 설정에서 단순화된 접근 방식이 좋습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/facade/response/StudentRecordCreateResponse.java (1)

3-11: 중복 모델은 레이어 분리를 위한 의도된 설계입니다.

StudentRecordCreateResponse는 외부 OpenAIVersionResponse를 내부 API 계층으로 매핑하기 위한 DTO이므로, 구조가 동일하더라도 별도 정의가 적절합니다. of() 팩토리 메서드도 일관된 생성 패턴으로 유용하며, 현재로선 변경이 필요하지 않습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/exception/StudentRecordErrorCode.java (1)

9-17: 에러 코드 정의가 적절합니다.

에러 코드 네이밍 컨벤션과 한국어 메시지가 일관성 있게 정의되어 있고, HTTP 상태 코드가 에러 코드에 적절히 반영되어 있습니다.

edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordController.java (1)

7-13: 아키텍처 분리 전략이 적절합니다.

CRUD와 AI 기반 생성 기능을 별도 컨트롤러로 분리하는 것은 좋은 설계 결정입니다. 향후 CRUD 구현 시 단일 책임 원칙을 유지할 수 있습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAIResult.java (1)

46-52: 팩토리 메서드 구현이 적절합니다.

현재 시간을 자동으로 설정하는 팩토리 메서드와 private 빌더 패턴이 잘 구현되어 있습니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/service/StudentRecordService.java (3)

28-33: AI 태스크 생성 로직이 적절합니다.

트랜잭션 처리와 엔티티 생성 로직이 올바르게 구현되어 있습니다.


35-38: 예외 처리가 적절합니다.

커스텀 에러 코드를 사용한 예외 처리가 일관성 있게 구현되어 있습니다.


22-26: N+1 쿼리 우려 불필요 (단건 조회)
getRecordDetail은 단건 조회만 수행하므로 반복문에 의한 N+1 쿼리 문제가 발생하지 않습니다. 현 상태에서 페치 조인 혹은 @EntityGraph 설정을 추가할 필요는 없습니다.
위 리뷰 코멘트는 해당 상황에 적용되지 않으니 무시해주세요.

Likely an incorrect or invalid review comment.

edukit-api/src/main/java/com/edukit/api/common/resolver/MemberIdArgumentResolver.java (2)

19-19: 타입 제한 변경이 일관성을 향상시킵니다.

boxed Long 타입 지원을 제거하고 primitive long 타입만 지원하도록 변경한 것은 일관성 측면에서 좋은 개선입니다.


24-27: SecurityContext principal 타입 안전성 확인 필요
현재 MemberIdArgumentResolver.resolveArgument(...)

return SecurityContextHolder.getContext().getAuthentication().getPrincipal();

를 그대로 반환하고 있습니다.
인증 시 principal이 반드시 Long으로 설정되어 있지 않다면 이후 사용 시 ClassCastException이 발생할 수 있습니다.

확인 및 조치 사항:

  • Authentication 구현체에서 getPrincipal() 반환 타입이 Long인지 검증
  • 만약 Long이 아닐 수 있다면 instanceof Long 체크 후 변환 로직 또는 예외 처리 추가
edukit-core/src/main/java/com/edukit/core/studentrecord/entity/StudentRecordAITask.java (1)

21-64: 엔티티 설계가 잘 구성되어 있습니다.

JPA 매핑과 관계 설정이 적절하며, 팩토리 메서드를 통한 생성 패턴도 좋습니다. Lazy loading 사용도 성능상 적절합니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (2)

16-22: 파사드 패턴을 통한 관심사 분리가 잘 구현되었습니다.

서비스들 간의 통합을 담당하는 파사드 역할이 명확하고, 의존성 주입도 적절합니다.


35-38: 현재 리액티브 메서드에 데이터베이스 작업이 없어 트랜잭션 경계 검토는 불필요합니다.

  • OpenAIService에서는 @Transactional, repository, save(), update(), delete() 등 DB 작업이 전혀 없습니다.
  • StudentRecordAIFacade#generateAIStudentRecordStream는 OpenAI 응답을 StudentRecordCreateResponse.of(...)로 매핑만 할 뿐, DB 접근 로직이 없습니다.

추후에 데이터베이스 저장·수정·삭제 로직이 추가될 경우, 스프링 리액티브 트랜잭션 관리(TransactionalOperator 등)를 도입하는 것을 권장드립니다.

edukit-core/src/main/java/com/edukit/core/studentrecord/util/AIPromptGenerator.java (1)

9-54: 상세한 프롬프트 템플릿이 잘 구성되었습니다.

AI에게 제공하는 지침이 매우 구체적이고 포괄적으로 작성되어 있어, 일관된 품질의 응답을 얻을 수 있을 것 같습니다. 특히 작성 금지 사항과 형식 지정이 명확합니다.

edukit-api/src/main/java/com/edukit/api/controller/studentrecord/StudentRecordAIController.java (1)

43-48: Server-Sent Events를 활용한 스트리밍 구현이 적절합니다.

MediaType.TEXT_EVENT_STREAM_VALUE를 사용한 SSE 구현과 Flux 기반 리액티브 스트리밍이 잘 구성되어 있습니다.

edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (2)

10-20: 클래스 구조와 의존성 주입이 적절합니다!

Spring의 모범 사례를 잘 따르고 있으며, 시스템 지시사항을 상수로 정의한 것도 좋습니다.


90-108: 콘텐츠 추출 로직이 깔끔합니다!

버전 마커 사이의 콘텐츠를 추출하는 로직이 잘 구현되어 있으며, 마지막 버전의 경우도 적절히 처리하고 있습니다.

@kgy1008 kgy1008 changed the title [EDMT-293] AI 응답 스트리밍 방식으로 변경 및 타임아웃 설정 [EDMT-293] AI 응답 스트리밍 방식으로 변경 Jul 28, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (3)

82-95: 마지막 버전 감지 로직 오류

현재 isVersionComplete 메서드가 버전 3(마지막 버전)에 대해 항상 false를 반환합니다. 이로 인해 스트리밍 중에는 버전 3이 완성되어도 감지되지 않고, 오직 스트림 완료 시에만 처리됩니다.

다음과 같이 수정하세요:

     private boolean isVersionComplete(String buffer, int currentVersion) {
         String currentVersionPattern = "===VERSION_" + (currentVersion + 1) + "===";
 
         if (!buffer.contains(currentVersionPattern)) {
             return false;
         }
 
         if (currentVersion < 2) {
             String nextVersionPattern = "===VERSION_" + (currentVersion + 2) + "===";
             return buffer.contains(nextVersionPattern);
+        } else {
+            // 마지막 버전의 경우 버전 마커 이후 충분한 컨텐츠가 있는지 확인
+            int contentStart = buffer.indexOf(currentVersionPattern) + currentVersionPattern.length();
+            String content = buffer.substring(contentStart).trim();
+            // 최소 길이 또는 종료 패턴 확인
+            return content.length() > 10 || content.contains("===END===");
         }
-
-        return false;
     }

117-131: 주석 처리된 이전 구현 제거 및 예외 처리 패턴 적용

Git 히스토리에서 이전 구현을 확인할 수 있으므로 주석을 제거하세요. 다만, 기존의 예외 처리 패턴은 새로운 스트리밍 메서드에도 적용해야 합니다.

110-124행의 주석을 모두 제거하고, 위의 getVersionedStreamingResponse 메서드에 제안한 예외 처리 패턴을 적용하세요.


24-80: 스트리밍 처리 시 메모리 안전성 및 예외 처리 강화 필요

현재 구현에서 다음 사항들을 개선해야 합니다:

  1. 버퍼 크기 제한이 없어 대용량 응답 시 메모리 문제 발생 가능
  2. 타임아웃 처리 미비
  3. 청크 처리 중 예외 발생 시 적절한 에러 전파 부족

다음과 같이 개선하세요:

+    private static final int MAX_BUFFER_SIZE = 100_000; // 100KB 제한
+    private static final Duration TIMEOUT = Duration.ofSeconds(30);
+
     public Flux<OpenAIVersionResponse> getVersionedStreamingResponse(final String prompt) {
         return Flux.create(sink -> {
             StringBuilder buffer = new StringBuilder();
             AtomicInteger currentVersion = new AtomicInteger(0);
 
             chatClient.prompt()
                     .system(SYSTEM_INSTRUCTIONS)
                     .user(prompt)
                     .stream()
                     .content()
+                    .timeout(TIMEOUT)
                     .subscribe(
                             chunk -> {
+                                try {
                                     buffer.append(chunk);
+                                    
+                                    // 버퍼 크기 체크
+                                    if (buffer.length() > MAX_BUFFER_SIZE) {
+                                        sink.error(new OpenAiException(OpenAiErrorCode.OPEN_AI_INTERNAL_ERROR, 
+                                            new IllegalStateException("응답 크기가 제한을 초과했습니다.")));
+                                        return;
+                                    }
+                                    
                                     String currentBuffer = buffer.toString();
 
                                     // 버전 완성 감지 및 전송 로직
                                     if (isVersionComplete(currentBuffer, currentVersion.get())) {
                                         // ... 기존 로직
                                     }
+                                } catch (Exception e) {
+                                    sink.error(new OpenAiException(OpenAiErrorCode.OPEN_AI_INTERNAL_ERROR, e));
+                                }
                             },
-                            sink::error,
+                            error -> {
+                                if (error instanceof TimeoutException) {
+                                    sink.error(new OpenAiException(OpenAiErrorCode.OPEN_AI_TIMEOUT, error));
+                                } else {
+                                    sink.error(new OpenAiException(OpenAiErrorCode.OPEN_AI_INTERNAL_ERROR, error));
+                                }
+                            },
🧹 Nitpick comments (1)
edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (1)

97-115: 버전 패턴 문자열을 상수로 정의하여 가독성 개선

현재 구현은 정확하지만, 버전 패턴 문자열이 여러 곳에서 반복되고 있습니다.

가독성과 유지보수성을 위해 다음과 같이 개선할 수 있습니다:

+    private static final String VERSION_PATTERN_PREFIX = "===VERSION_";
+    private static final String VERSION_PATTERN_SUFFIX = "===";
+    
+    private static String getVersionPattern(int versionNumber) {
+        return VERSION_PATTERN_PREFIX + versionNumber + VERSION_PATTERN_SUFFIX;
+    }
+
     private String extractCompleteVersion(String buffer, int versionNumber) {
-        String versionPattern = "===VERSION_" + versionNumber + "===";
-        String nextVersionPattern = "===VERSION_" + (versionNumber + 1) + "===";
+        String versionPattern = getVersionPattern(versionNumber);
+        String nextVersionPattern = getVersionPattern(versionNumber + 1);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78e2185 and 8264ce1.

📒 Files selected for processing (8)
  • edukit-api/src/main/java/com/edukit/api/controller/studentrecord/request/StudentRecordPromptRequest.java (1 hunks)
  • edukit-api/src/main/java/com/edukit/api/security/authentication/MemberDetailReader.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/repository/MemberRepository.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/service/MemberService.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/studentrecord/util/AIPromptGenerator.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (1 hunks)
  • edukit-external/src/main/java/com/edukit/external/ai/exception/OpenAiErrorCode.java (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • edukit-external/src/main/java/com/edukit/external/ai/exception/OpenAiErrorCode.java
  • edukit-api/src/main/java/com/edukit/api/controller/studentrecord/request/StudentRecordPromptRequest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • edukit-api/src/main/java/com/edukit/api/security/authentication/MemberDetailReader.java
  • edukit-core/src/main/java/com/edukit/core/member/service/MemberService.java
  • edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java
  • edukit-core/src/main/java/com/edukit/core/studentrecord/util/AIPromptGenerator.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
edukit-external/src/main/java/com/edukit/external/ai/OpenAIService.java (2)
edukit-external/src/main/java/com/edukit/external/ai/exception/OpenAiException.java (1)
  • OpenAiException (5-14)
edukit-core/src/main/java/com/edukit/core/studentrecord/facade/StudentRecordAIFacade.java (1)
  • Service (15-61)
🔇 Additional comments (1)
edukit-core/src/main/java/com/edukit/core/member/repository/MemberRepository.java (1)

13-13: 코드 변경사항이 적절합니다.

새로 추가된 findByIdAndIsDeleted 메서드는 Spring Data JPA 명명 규칙을 올바르게 따르고 있으며, 기존 메서드들과 일관된 패턴을 유지하고 있습니다. soft-delete 시나리오에서 ID와 삭제 상태를 기반으로 멤버를 조회하는 기능이 적절히 구현되어 있고, 새로운 AI 학생부 생성 기능의 권한 검증 로직을 지원하는 중요한 역할을 합니다.

Copy link
Member

@TaegeunYou TaegeunYou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

.requestMatchers(SecurityWhitelist.AUTH_WHITELIST).permitAll()
.requestMatchers(SecurityWhitelist.BUSINESS_WHITE_LIST).permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/api/v2/student-records/ai-generate/**").permitAll()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

permitAll() 설정은 의도하신 걸까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants