- 서비스명: DailyQ
- 목표: 취업 준비생이 하루 5분, 하루에 하나씩 면접 질문에 답변하면서
- 꾸준히 연습하는 습관을 만들고
- AI 피드백을 통해 부족한 점을 보완하며
- 면접 실력을 끌어올릴 수 있게 함.
- 핵심 가치: 무겁지 않은 '1일 1문항' 루틴 → 꾸준한 학습 → 성장 체감 → 동기 유지
- 취업 준비생의 학습 패턴 문제
- 면접 대비를 꾸준히 하고 싶지만, 매일 무엇을 준비해야 할지 체계가 없음
- 자기 답변을 기록하거나 피드백 받을 기회가 부족해, 자신의 성장 정도를 측정하기 어려움
- 혼자 공부하다 보니 지루해지고, 동기부여가 금방 떨어져 학습을 중단하는 경우가 많음
- 기존 서비스의 한계
- 모의 면접(예: 사람인 AI 면접)
- 1회 단가가 높아 자주 이용하기 어렵고,
- 실제 학습 루틴으로 이어지기보다는 일회성 체험에 머무는 경우가 많음
- 메일 기반 학습(예: 매일메일)
- 사용자가 메일을 열어보지 않으면 학습이 끊김 → 사용자 주도형(passive) 구조라 지속성이 낮음
- 짧은 팁 위주의 정보 제공이라, 실제 답변 훈련/피드백 루프가 부족함
- 모의 면접(예: 사람인 AI 면접)
- 결과적으로
- 취업 준비생은 꾸준히 연습할 수 있는 가벼운 루틴과
- 즉각적인 피드백과 성취감을 제공하는 도구를 찾지 못해
- 학습 지속률이 낮고, 실질적인 역량 향상으로 이어지지 못함
- 브라우저/앱 실행 시 자동으로 하루 1문항 노출
- 답변은 텍스트/음성 중 선택 가능 → 음성의 경우 시간 제한 옵션 제공
- AI 피드백으로 답변의 구조·명확성·근거 강화를 지원
- 아카이브/스트릭/라이벌 기능으로 성취감·경쟁심 제공
- 꼬리 질문 기능으로 실제 면접과 유사도 확보
- 회원/온보딩
- 소셜 로그인(구글/카카오)
- 원하는 직군 선택 → 직군 내 세부직군 다중 선택
- 하루 질문 루틴
- 메인 페이지 [오늘의 질문] → 답변 입력(타자/음성)
- 답변 입력(타자/음성), 음성의 경우 시간 제한
- 음성 답변은 자동 STT 처리
- AI 피드백 + 사용자가 난이도 체크
- 스트릭 증가
- 아카이브
- 내가 푼 답변/피드백 히스토리
- 즐겨찾기(핀)/난이도 필터
- 간단한 메모 작성 기능
- 동기 부여
- 스트릭(연속 기록)
- 라이벌 지정
- 유료 서비스
- (구독) 일일 질문 한도 추가
- 면접이 임박한 사용자의 편의성 증대
- (구독) 일일 질문 한도 추가
신규 사용자는 소셜 로그인을 통해 간편하게 가입하고, 온보딩 과정을 통해 맞춤형 서비스를 설정합니다.
주요 단계:
- 소셜 로그인 시작: 사용자가 구글 또는 카카오 로그인 버튼을 클릭합니다.
- OAuth2 인증: Spring Security가 OAuth2 인증 플로우를 시작하고, 사용자를 구글/카카오 인증 페이지로 리다이렉트합니다.
- 사용자 인증: 사용자가 구글/카카오에서 로그인하고 정보 제공에 동의합니다.
- 사용자 정보 조회:
CustomOAuth2UserService가 Authorization Code를 Access Token으로 교환하고, 사용자 정보 API를 호출합니다. - 사용자 저장/업데이트: 이메일로 기존 사용자를 조회하고, 없으면 신규 생성, 있으면 이름만 업데이트합니다.
- 신규 사용자 초기화: 신규 사용자인 경우
UserPreferences와UserFlowProgress를 기본값으로 생성합니다. - JWT 토큰 발급:
OAuth2AuthenticationSuccessHandler에서 Access Token과 Refresh Token을 생성합니다. - 토큰 저장: Refresh Token은 HttpOnly 쿠키에 저장되고, Access Token은 URL 파라미터로 프론트엔드에 전달됩니다.
- 온보딩 진행: 신규 사용자는 직군 선택을 진행합니다.
- 서비스 이용 시작: 온보딩 완료 후 메인 페이지에서 오늘의 질문을 받아 답변을 시작할 수 있습니다.
토큰 갱신 플로우:
- Access Token 만료 시:
POST /api/token/refresh로 Refresh Token을 사용해 새로운 Access Token 발급 - Refresh Token은 쿠키에서 자동으로 읽어옴
사용자가 메인 페이지에서 오늘의 질문을 받아오는 과정입니다.
주요 단계:
- 질문 요청: 클라이언트가
GET /api/questions/random엔드포인트로 랜덤 질문을 요청합니다. - 사용자 설정 조회: 서버가
UserPreferences를 조회하여 사용자의 질문 모드(TECH/FLOW), 직군 등을 확인합니다. - 일일 한도 검증: 오늘 이미 답변한 질문 수를 확인하여 일일 질문 한도를 초과하지 않았는지 검증합니다.
- 질문 모드별 처리:
- TECH 모드: 사용자의 직군에 맞는 기술 질문을 랜덤으로 선택합니다. 이미 답변한 질문은 제외됩니다.
- FLOW 모드: 사용자의 현재 면접 단계(INTRO → MOTIVATION → TECH → PERSONALITY)에 맞는 질문을 선택합니다.
UserFlowProgress를 통해 현재 단계를 추적합니다.
- 꼬리 질문 우선 처리: 미답변 꼬리 질문이 있으면 일반 질문보다 우선적으로 제공합니다.
- 질문 반환: 선택된 질문과 함께 질문 모드, 현재 단계, 시간 제한 등의 정보를 포함한
RandomQuestionResponse를 반환합니다.
음성 답변의 경우 비동기 STT 처리와 실시간 알림을 통해 사용자 경험을 최적화합니다.
주요 단계:
- 음성 녹음: 사용자가 브라우저에서 음성을 녹음합니다.
- Presigned URL 요청: 클라이언트가 서버에 업로드용 Presigned URL을 요청합니다.
- 직접 업로드: 클라이언트가 Presigned URL을 사용해 NCP Object Storage에 음성 파일을 직접 업로드합니다.
- 답변 등록: 음성 파일 URL과 함께
POST /api/answers엔드포인트로 답변을 등록합니다. - SSE 연결: 답변 제출과 동시에, 클라이언트가 SSE 전용 일회용 토큰을 발급받고 이를 사용해
GET /api/sse/connect로 SSE 연결을 수립하여 실시간 이벤트를 받을 준비를 합니다. - Answer 생성: 서버에서 Answer 엔티티를 생성하지만, 아직 텍스트는 없고 상태는
PENDING_STT입니다. - STT 작업 시작:
SttTask를 생성하고 NCP CLOVA SPEECH API를 호출하여 비동기 변환 작업을 시작합니다. - STT 콜백 처리: NCP CLOVA가 변환을 완료하면
POST /api/stt/callback으로 콜백을 보냅니다. - 텍스트 업데이트: 콜백에서 받은 텍스트로 Answer를 업데이트하고 SSE를 통해 클라이언트에 STT 완료 이벤트를 전송합니다.
- AI 피드백 생성:
FeedbackService가 OpenAI API를 호출하여 피드백을 생성합니다. - 결과 조회: 클라이언트가
GET /api/answers/{id}로 최종 답변과 피드백을 조회합니다. 응답에는answerText,feedback(AI 피드백),question정보가 포함됩니다.
에러 처리:
- STT 실패 시:
SttFailedEvent발행 → SSE로 알림 → 클라이언트가/api/answers/{id}/retry-stt로 재시도 가능 - 피드백 생성 실패 시: Feedback 상태를
FAILED로 업데이트 → 재시도 가능
텍스트 답변은 더 단순한 동기 플로우를 따릅니다.
주요 단계:
- 텍스트 입력: 사용자가 브라우저에서 텍스트로 답변을 입력합니다.
- 답변 등록: 클라이언트가
POST /api/answers엔드포인트로 답변을 등록합니다. - Answer 생성: 서버에서 Answer 엔티티를 생성합니다. 이 시점에 이미 텍스트가 포함되어 있으며,
feedback상태는PENDING입니다. - AI 피드백 생성:
FeedbackService가 OpenAI GAPI를 호출하여 피드백을 생성합니다. - 결과 조회: 클라이언트가
GET /api/answers/{id}로 최종 답변과 피드백을 조회합니다. 응답에는answerText,feedback(AI 피드백),question정보가 포함됩니다.
사용자의 답변을 바탕으로 AI가 추가 질문을 생성하여 실제 면접과 유사한 경험을 제공합니다.
주요 단계:
- 꼬리 질문 생성 요청: 사용자가 답변에 대한 피드백을 확인한 후,
POST /api/questions/followUp/{answerId}엔드포인트로 꼬리 질문 생성을 요청합니다. - 답변 및 질문 조회: 서버가 해당 Answer와 원본 Question 정보를 조회합니다.
- AI 질문 생성:
FollowUpQuestionService가 OpenAI API를 호출하여 사용자의 답변을 분석하고, 답변을 더 깊이 있게 탐구할 수 있는 꼬리 질문을 생성합니다. - 꼬리 질문 저장: 생성된 꼬리 질문들을
FollowUpQuestion엔티티로 저장하고, 원본 Answer와 연결합니다. - 응답 반환: 생성된 꼬리 질문의 개수를 포함한
FollowUpGenerationResponse를 반환합니다. - 질문 조회 시 우선 제공: 이후 클라이언트트가
GET /api/questions/random을 호출하면, 미답변 꼬리 질문이 일반 질문보다 우선적으로 제공됩니다.
꼬리 질문의 특징:
- 사용자의 답변 내용을 바탕으로 맥락에 맞는 추가 질문 생성
- 실제 면접에서 면접관이 할 수 있는 심화 질문 시뮬레이션
- 답변의 깊이와 완성도를 높이는 데 도움
SSE + virtual thread
- CLOVA STT 변환은 비동기 작업으로 이 결과를 클라이언트에게 '실시간'으로 알려주기 위해 SSE를 도입
- 이때, SSE는 특성상 각 사용자가 접속해 있는 내내 서버 스레드 1개를 점유함
- 테스트에서 동시 접속자가 200명만 넘어가도 모든 OS 쓰레드가 고갈되는 문제가 발생하여 서버 전체가 다운되는 현상 발생
- Virtual Thread를 도입하여 SSE연결을 유지하더라도 OS를 점유하지 않고 10~20개의 적은 OS쓰레드만으로 n천개의 SSE 동시 연결을 처리하도록 함
- 30초간 쓰레드를 강제로 대기시키는 즉, SSE 연결 후 대기 시뮬레이션을 위한 테스트용 API를 구현
- 기존 OS 쓰레드의 한계치를 넘기게 3000명이 동시요청하도록 설정하여 테스트
- 즉 서버가 3000개의 쓰레드가 동시에 블로킹된 상태를 버티도록 구성
- 사진(위) virtual thread를 켠 경우 OS 쓰레드 고갈 없이 모두 성공
- 사진 (아래) 가상유저가 204명에 도달하자마자 OS 쓰레드 고갈로 나머지 요청 모두 실패
nGrinder 도입
- FE가 API가 느리다고 리포트하여, BE팀은 정확한 원인(API 문제/DB 문제/인프라 문제)을 재현하길 원함
- Groovy 스크립트 기반 시나리오: VUser(가상 유저)별로 복잡한 로직을 코드로 작성
- 추적 및 관리 : API 엔드포인트별로 분리
- 명확한 병목 시각화: VUser 증가에 따른 Error Rate, TPS, Mean Test Time(MTT)을 실시간 그래프로 확인
- 명확한 SLA제공(BE) - 데이터에 기반한 명확한 SLA(Service Level Agreement) 제공
- 안티패턴 방지 및 신뢰 구축(FE) - FE팀이 "API가 느릴 것"이라 지레짐작하여 불필요한 클라이언트 사이드 캐싱이나 복잡한 상태 관리를 구현하는 안티패턴을 방지
JWT 기반 로그인 인증/인가
- JWT(JSON Web Token) 도입의 주된 이유는 기존 세션(Session) 기반 인증 방식의 한계점을 극복하고, 확장성 및 효율성을 개선하기 위함
- 로그인 성공 시 서버가 비밀키를 사용해 JWT를 발급하고, 클라이언트는 해당 토큰을 저장 후 요청 시 헤더
(Authorization: Bearer <token>)에 포함 - 서버는 토큰 검증만 수행하며, 별도의 세션 상태를 유지하지 않음 (Stateless 인증 구조)
- 토큰에 사용자 권한(Role) 및 만료 시간(Expiration Time)을 포함하여 인가(Authorization) 를 간편하게 처리
- Refresh Token을 활용해 Access Token 재발급 프로세스 구현으로 보안성과 편의성 강화
- 서버에 세션 저장이 불필요하므로 확장성과 성능 향상
- REST API, 모바일, 프론트엔드 등 다양한 클라이언트 환경에서 일관된 인증 체계 유지
- 토큰 기반 검증으로 보안성 강화(만료 시간, 서명 검증, HTTPS 연동 등)
관리자 페이지 구현
- 시스템 운영 중 직접 DB 접근이나 개발자 의존적 절차로 수행해야 하는 비효율 존재
- 보안·정책·콘텐츠 관리 등 운영자가 직접 제어할 수 있는 인터페이스 부재
- JWT 기반 인증/인가 연동으로 접근 제어 강화
- 회원 조회 및 수정/삭제
- 이름 및 역할(Role)별 접근 권한 관리 기능 제공 (관리자/구독자/일반사용자)
- 직군/직업 조회 및 추가/삭제
- 질문 조회 및 추가/수정/삭제
- 질문 내용, 질문 타입(TECH/INTRO/MOTIVATION/PERSONALITY), 연결된 직업 종류, 활성화 여부(활성/비활성) 관리 제공
- 운영자가 직접 관리 가능한 인터페이스 제공으로 운영 효율성 향상
- 관리자 권한 분리 및 인증 강화로 보안 강화
- 데이터 및 사용자 관리 자동화로 개발자 의존도 감소
- 장애 대응 및 정책 변경 속도 향상으로 운영 민첩성 확보
전역 에러 핸들러
- 보일러 플레이트 코드 발생 : API 컨트롤러마다
try-catch문이 반복 - 일관성 없는 에러 응답
- 디버깅에 도움이 되지 않는 로그 : log.error("유저를 찾지 못함")은 도움이 되지 않음
- ExceptionHandlerAdvice를 통해 에러 핸들링
- BusinissException과 InfraException 패턴을 통한 에러 처리
- 코드 중복 제거: 컨트롤러에서 예외 처리 코드 제거
- 일관된 응답 형식: 클라이언트가 동일한 형식으로 처리 가능
- 유지보수성 향상: 예외 처리 로직 변경 시 한 곳만 수정
- 디버깅 용이: 모든 예외가 구조화된 로그로 기록
- 보안: 예상치 못한 예외의 상세 정보 노출 방지
템플릿 메서드 패턴
AnswerCommandService의 submitAnswer 메서드에서 일반 질문과 꼬리 질문 처리가 하나의 메서드에 혼재
- 코드 중복: 공통 로직(답변 객체 생성, 저장, STT 처리)이 반복됨
- 복잡한 분기: if-else로 일반/꼬리 질문을 구분하며 가독성 저하
- 확장성 부족: 새로운 답변 타입 추가 시 메서드 수정 필요
- 단일 책임(SRP) 위반: 하나의 메서드가 여러 타입의 답변 처리 로직을 포함
- 템플릿 메서드 패턴을 적용해 공통 흐름은 추상 클래스에서 정의하고, 차이점은 하위 클래스에서 구현하도록 분리
- 코드 재사용성 향상: 공통 로직을 한 곳에서 관리
- 가독성 개선: 각 핸들러가 자신의 책임만 담당
- 확장성: 새로운 답변 타입 추가 시 핸들러만 추가
- 유지보수성: 변경 영향 범위가 명확
- 테스트 용이성: 각 핸들러를 독립적으로 테스트 가능
- 현재
AnswerCommandService는 팩토리에서 핸들러를 받아handle()만 호출
cursor 기반 랜덤 질문
- 기존 방식(전체 질문 조회 후 랜덤 선택)은 질문 수가 증가할수록 O(n) 메모리 사용과 느린 쿼리 성능 문제가 발생
- 특히 사용자가 이미 답변한 질문을 제외하는 조건을 포함할 경우 쿼리 비용이 비약적으로 증가
- MAX ID를 먼저 조회하여 사용 가능한 질문의 ID 범위를 파악
- 1부터 MAX ID 사이의 랜덤 ID 생성
- Cursor 기반 조회(id >= randomId)로 해당 범위에서 첫 번째 질문 선택
- 인덱스를 활용한 효율적인 쿼리 (ORDER BY id, LIMIT 1)
- O(1) 메모리 사용: 전체 질문을 메모리에 로드하지 않음
- 빠른 쿼리 성능: 인덱스 기반 범위 스캔으로 O(log n) 시간 복잡도
- 확장성 향상: 질문 수가 수만 개로 증가해도 일정한 성능 유지
- 랜덤성 보장: 각 질문이 동일한 확률로 선택됨
QueryDSL이 아닌 JPA Specification 도입으로 cursor 기반 무한스크롤 구현 + Slice
- 전통적 Pagination(Offset)의 한계: '아카이브' 기능은 사용자의 모든 답변을 조회해야 함
- 페이지 번호가 늘어날 수록 OFFSET N개 만큼의 데이터 스캔이 발생하여 조회 성능 저하
- UX 관점에서 안티패턴이 발생
- 동적 필터 요구 : FE에서 날짜, 직무, 질문 타입, 즐겨찾기, 난이도 등 다양한 조건으로 동적 검색 요구
- JPA Specification : JPA 표준 기능으로 의존성, 빌드, 학습 비용 최소화
DTO를 구성하여Predicate를 조합하는createSpecification메서드를 통해 유지보수 확보
- 무한 스크롤 : Cursor + Slice
- OFFSET 대신 lastId와 lastCreatedAt을 조합하여 마지막으로 본 데이터 다음 10개를 조회하는 방식 채택
- Page는 불필요한 COUNT(*) 쿼리를 추가로 실행하여 성능 저하 -> Slice를 통해 COUNT쿼리를 제거하고 hasNext를 FE에게 전달
- BE 성능: 데이터가 수만 건이 존재하여도 OFFSET 스캔이 없으므로, 항상 일정하고 빠른 조회 성능 보장
- BE 유지보수: QueryDSL 도입 대비 학습 비용 낮추기
- FE/UX: 사용자에게 끊임 없는 무한 스크롤 경험 제공
NCP Object Storage 보안: Pre-Signed URL 도입
- CLOVA SPEECH API는 파일 경로(dataKey)를 인자로 받기 때문에, 음성 파일이 Object Storage에 먼저 업로드되어야 함
- 서버 부하: FE → BE → Storage로 파일을 중계하면 백엔드 부하가 심각
- 보안 취약: FE → Storage로 직접 업로드하려면, FE에 Secret Key가 노출되어 치명적
-
백엔드가 Secret Key를 안전하게 보관하면서, FE에게 "10분 동안, 특정 경로에, PUT 요청만 허용하는" 임시 업로드 URL을 발급.
-
FE → BE: 업로드 URL 요청 (/api/answers/upload-url)
-
BE → FE: Pre-signed URL 생성 및 반환
-
FE → NCP Storage: FE가 이 임시 URL을 사용해 스토리지로 파일을 직접 PUT (업로드)
-
(이후) FE → BE: "업로드 완료" 응답 이후 STT 요청 (이때 BE가 Clova API 호출)
-
추가 핵심 보안 강화 조치
- 사용자별 경로 격리 (User Scoping)
- 문제: 모든 사용자가 같은 곳에 업로드하면 파일이 덮어써지거나 경로 조작 공격이 가능
- 해결: 파일 경로(objectKey)를 **uploads/{userId}/{UUID}.[확장자]**로 강제하여 논리적으로 격리
- 파일 확장자 검증 (Allow-list)
- 문제: .html, .exe 같은 악성 파일 업로드 시도 위헙
- 해결: Clova가 지원하는 오디오 형식(.mp3, .m4a 등)의 '허용 목록'과 비교 검증 후 목록에 없으면 400 Bad Request를 반환하여 업로드를 원천 차단
- 사용자별 경로 격리 (User Scoping)
CORS정책 및 Preflight 해결
- CORS 정책 위반: 브라우저와 서버의 Origin이 달라, 브라우저의 동일 출처 정책(SOP)에 의해 API 요청이 기본적으로 차단됨
- Preflight (OPTIONS) 요청 발생: 본 요청(POST, PUT 등)에 Authorization 헤더(JWT 토큰)를 포함시킴
- Authorization 헤더는 Simple Request 조건에 해당하지 않으므로, 브라우저는 본 요청 전 서버의 허용 여부를 묻는 OPTIONS 메서드(Preflight) 요청을 먼저 전송함
- 브라우저가 보낸 OPTIONS 요청에는 인증 토큰(Authorization 헤더)이 없음
- 따라서 JwtAuthenticationFilter 이전에 Spring Security의 인증 체인이 먼저 동작하여, 이 OPTIONS 요청을 401 Unauthorized 또는 403 Forbidden으로 차단함
- 브라우저는 Preflight 요청이 실패(200 OK가 아님)했으므로, 본 요청(POST 등)을 보내지 않고 CORS 에러를 발생시킴
- OPTIONS 요청에 대한 Spring Security 인증 해제
- filterChain 메서드 내에서 authorizeHttpRequests 설정을 통해 모든 OPTIONS 메서드 요청은 인증 절차 없이 통과(permit)시킴
- 구체적인 CORS 정책 정의 및 적용
- 허용 출처 (Origins): application.yml에 정의된 프론트엔드 도메인 목록을 허용
- 허용 메서드 (Methods): OPTIONS를 포함한 GET, POST, PUT, DELETE 등 모든 메서드 허용
- 허용 헤더 (Headers): Authorization 헤더를 포함한 모든 헤더 허용 (setAllowedHeaders(List.of("*")))
- 자격 증명 (Credentials): 쿠키(예: OAuth refresh_token)를 주고받을 수 있도록 허용 (setAllowCredentials(true))
- filterChain 내에서 .cors(cors -> cors.configurationSource(corsConfigurationSource))를 호출하여, Spring Security가 이 CORS 규칙을 사용하도록 설정
- Preflight 요청 처리: 브라우저가 OPTIONS 요청을 보내면, Spring Security의 permitAll 규칙 덕분에 인증 없이 통과
- 본 요청 처리: 브라우저가 Preflight 성공을 확인하고 Authorization 헤더가 포함된 실제 POST 요청을 전송
- 이 요청은 OPTIONS가 아니므로 permitAll 규칙에 걸리지 않고, anyRequest().authenticated() 규칙에 따라 인증이 필요
- JwtAuthenticationFilter가 토큰을 성공적으로 검증하여 인증을 완료시키고, 컨트롤러까지 요청이 정상적으로 도달
주요 컴포넌트:
- 클라이언트: Vite React 기반 웹앱 또는 Chrome Extension
- Spring Boot 서버: 비즈니스 로직 처리 및 API 제공
- NCP Object Storage: 음성 파일 저장소
- NCP CLOVA STT: 음성을 텍스트로 변환
- OpenAI GPT: 답변에 대한 AI 피드백 생성
- MySQL: Answer, Feedback, Question 등 데이터 영구 저장
| Java v21 | MySQL v8.0 | Spring v3.5.5 | Docker v27.3.1 | H2 v2.2.224 | nGrinder v3.5.9 |
|---|
- 프레임워크: Spring Boot 3.5.5
- 언어: Java 21
- 빌드 도구: Gradle 8.11
- 인증: Spring Security OAuth2 Client, JWT (jjwt 0.11.5)
- 데이터베이스: MySQL 8.0 (InnoDB)
- ORM: Spring Data JPA
- AI 연동:
- OpenAI GPT (Spring AI 1.0.0)
- NCP CLOVA STT (음성→텍스트 변환)
- 스토리지: NCP Object Storage (AWS S3 호환)
- 모니터링: Spring Actuator, nGrinder 3.5.9
- 문서화: SpringDoc OpenAPI 2.8.1
- 비동기 통신: Server-Sent Events (SSE)
- 프레임워크: React (TypeScript)
- 확장 프로그램: Chrome Extension
- 컨테이너화: Docker, Docker Compose
- 이미지 빌드: Jib
- CI/CD: GitHub Actions
- 배포: SSH 기반 자동 배포
dailyq/
├── src/main/java/com/knuissant/dailyq/
│ ├── config/ # 설정 클래스 (Security, OAuth2, JWT 등)
│ ├── controller/ # REST API 컨트롤러
│ ├── domain/ # 도메인 엔티티 (Answer, Question, User 등)
│ ├── dto/ # 데이터 전송 객체
│ ├── event/ # 이벤트 처리
│ ├── exception/ # 예외 처리 및 에러 코드
│ ├── external/ # 외부 API 연동 (GPT, NCP)
│ ├── jwt/ # JWT 토큰 생성 및 검증
│ ├── repository/ # 데이터 접근 계층 (JPA Repository)
│ └── service/ # 비즈니스 로직 계층
├── src/main/resources/
│ ├── application.yml # 애플리케이션 설정
│ ├── prompts/ # AI 프롬프트 템플릿
│ └── static/ # SQL 스크립트 (스키마, 목 데이터)
└── build.gradle # 의존성 관리
- Java 21 이상
- Gradle 8.11 이상
- Docker 및 Docker Compose
- MySQL 8.0
- 사용자: 매일 꾸준한 연습 → 성장 체감 → 동기 유지 → 취업 성공 확률↑
- 운영자: 데이터 축적(답변/난이도/자소서) → AI 학습 자원 확보 → 서비스 고도화
- 시장 경쟁력: 기존 모의면접 대비 가볍고, 메일 기반 대비 강제성이 있는 '데일리 루틴' 차별화
- 멀티모달 피드백
- 현재는 텍스트 위주의 피드백 → 톤 분석까지 확장
- 음성 데이터를 기반으로 태도·목소리 안정성까지 분석
- 자소서 기반 맞춤 질문 생성
- 사용자 자소서 업로드 → N개 핵심 문장 추출 → 직군/기업 컨텍스트로 질문 생성







