mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 04:49:11 +00:00
Chore: 회의 종료 API 실제 데이터 연동
This commit is contained in:
@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -327,9 +328,12 @@ public class MeetingService implements
|
||||
log.debug("Found minutes: {}", minutes.getTitle());
|
||||
}
|
||||
|
||||
// 5. AI 분석 수행 (현재는 Mock 데이터로 구현)
|
||||
MeetingAnalysis analysis = performAIAnalysis(meeting, minutes);
|
||||
// 5. 기본 분석 정보 생성 (실제 AI 분석은 AI 서비스에서 비동기 처리)
|
||||
MeetingAnalysis analysis = createBasicAnalysis(meeting, minutes);
|
||||
meetingAnalysisWriter.save(analysis);
|
||||
|
||||
// TODO: AI 서비스에 비동기 분석 요청 전송
|
||||
// aiAnalysisClient.requestAnalysis(meeting.getMeetingId(), minutes.getMinutesId());
|
||||
|
||||
// 6. 결과 DTO 구성
|
||||
MeetingEndDTO result = buildMeetingEndDTO(meeting, analysis);
|
||||
@@ -339,55 +343,20 @@ public class MeetingService implements
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 분석 수행 (Mock 구현)
|
||||
* 간단한 회의 분석 수행 (AI 서비스 없이 기본 정보만 처리)
|
||||
* AI 분석은 별도 AI 서비스에서 비동기로 처리되어야 함
|
||||
*/
|
||||
private MeetingAnalysis performAIAnalysis(Meeting meeting, Minutes minutes) {
|
||||
log.info("Performing AI analysis for meeting: {}", meeting.getMeetingId());
|
||||
|
||||
// Mock 데이터로 구현 (실제로는 AI 서비스 호출)
|
||||
List<String> keywords = List.of(
|
||||
"#신제품기획", "#예산편성", "#일정조율",
|
||||
"#시장조사", "#UI/UX", "#개발스펙"
|
||||
);
|
||||
|
||||
List<MeetingAnalysis.AgendaAnalysis> agendaAnalyses = List.of(
|
||||
MeetingAnalysis.AgendaAnalysis.builder()
|
||||
.agendaId("agenda-1")
|
||||
.title("1. 신제품 기획 방향성")
|
||||
.aiSummaryShort("타겟 고객을 20-30대로 설정, UI/UX 개선 집중")
|
||||
.discussion("신제품의 주요 타겟 고객층을 20-30대 직장인으로 설정하고, 기존 제품 대비 UI/UX를 대폭 개선하기로 함")
|
||||
.decisions(List.of("타겟 고객: 20-30대 직장인", "UI/UX 개선을 최우선 과제로 설정"))
|
||||
.pending(List.of())
|
||||
.extractedTodos(List.of("시장 조사 보고서 작성", "UI/UX 개선안 초안 작성"))
|
||||
.build(),
|
||||
MeetingAnalysis.AgendaAnalysis.builder()
|
||||
.agendaId("agenda-2")
|
||||
.title("2. 예산 편성 및 일정")
|
||||
.aiSummaryShort("총 예산 5억, 개발 기간 6개월 확정")
|
||||
.discussion("신제품 개발을 위한 총 예산을 5억원으로 책정하고, 개발 기간은 6개월로 확정함")
|
||||
.decisions(List.of("총 예산: 5억원", "개발 기간: 6개월", "예산 배분: 개발 60%, 마케팅 40%"))
|
||||
.pending(List.of("세부 일정 확정은 다음 회의에서 논의"))
|
||||
.extractedTodos(List.of("세부 개발 일정 수립"))
|
||||
.build(),
|
||||
MeetingAnalysis.AgendaAnalysis.builder()
|
||||
.agendaId("agenda-3")
|
||||
.title("3. 기술 스택 및 개발 방향")
|
||||
.aiSummaryShort("React 기반 프론트엔드, AI 챗봇 기능 추가")
|
||||
.discussion("프론트엔드는 React 기반으로 개발하고, 고객 지원을 위한 AI 챗봇 기능을 추가하기로 함")
|
||||
.decisions(List.of("프론트엔드: React 기반", "AI 챗봇 기능 추가", "Next.js 도입 검토"))
|
||||
.pending(List.of("AI 챗봇 학습 데이터 확보 방안"))
|
||||
.extractedTodos(List.of("AI 챗봇 프로토타입 개발", "Next.js 도입 검토 보고서"))
|
||||
.build()
|
||||
);
|
||||
private MeetingAnalysis createBasicAnalysis(Meeting meeting, Minutes minutes) {
|
||||
log.info("Creating basic analysis for meeting: {}", meeting.getMeetingId());
|
||||
|
||||
// 기본 분석 정보만 생성 (키워드나 상세 분석은 AI 서비스에서 별도 처리)
|
||||
return MeetingAnalysis.builder()
|
||||
.analysisId(UUID.randomUUID().toString())
|
||||
.meetingId(meeting.getMeetingId())
|
||||
.minutesId(minutes.getMinutesId())
|
||||
.keywords(keywords)
|
||||
.agendaAnalyses(agendaAnalyses)
|
||||
.status("COMPLETED")
|
||||
.completedAt(LocalDateTime.now())
|
||||
.keywords(List.of()) // AI 서비스에서 별도로 채워짐
|
||||
.agendaAnalyses(List.of()) // AI 서비스에서 별도로 채워짐
|
||||
.status("PENDING") // AI 처리 대기 상태
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
@@ -396,10 +365,11 @@ public class MeetingService implements
|
||||
* MeetingEndDTO 구성
|
||||
*/
|
||||
private MeetingEndDTO buildMeetingEndDTO(Meeting meeting, MeetingAnalysis analysis) {
|
||||
// 회의 시간 계산 (Mock)
|
||||
int durationMinutes = 90;
|
||||
// 회의 시간 및 참석자 수 계산 (실제 데이터 기반)
|
||||
int durationMinutes = calculateActualDuration(meeting);
|
||||
int participantCount = calculateActualParticipantCount(meeting.getMeetingId());
|
||||
|
||||
// 전체 Todo 개수 계산
|
||||
// 전체 Todo 개수 계산 (AI 분석 완료되기 전까지는 0)
|
||||
int totalTodos = analysis.getAgendaAnalyses().stream()
|
||||
.mapToInt(agenda -> agenda.getExtractedTodos().size())
|
||||
.sum();
|
||||
@@ -424,7 +394,7 @@ public class MeetingService implements
|
||||
|
||||
return MeetingEndDTO.builder()
|
||||
.title(meeting.getTitle())
|
||||
.participantCount(meeting.getParticipants() != null ? meeting.getParticipants().size() : 0)
|
||||
.participantCount(participantCount)
|
||||
.durationMinutes(durationMinutes)
|
||||
.agendaCount(analysis.getAgendaAnalyses().size())
|
||||
.todoCount(totalTodos)
|
||||
@@ -460,6 +430,36 @@ public class MeetingService implements
|
||||
return updatedMeeting;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의 실제 진행 시간 계산
|
||||
*/
|
||||
private int calculateActualDuration(Meeting meeting) {
|
||||
if (meeting.getStartedAt() != null && meeting.getEndedAt() != null) {
|
||||
// 실제 시작/종료 시간이 있으면 실제 진행 시간 계산
|
||||
Duration duration = Duration.between(meeting.getStartedAt(), meeting.getEndedAt());
|
||||
return (int) duration.toMinutes();
|
||||
} else if (meeting.getScheduledAt() != null && meeting.getEndTime() != null) {
|
||||
// 예정 시간 기준으로 계산
|
||||
Duration duration = Duration.between(meeting.getScheduledAt(), meeting.getEndTime());
|
||||
return (int) duration.toMinutes();
|
||||
} else {
|
||||
// 기본값 (1시간)
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 참석자 수 계산
|
||||
*/
|
||||
private int calculateActualParticipantCount(String meetingId) {
|
||||
// 실제 참석한 참석자 수 계산 (meeting_participants 테이블에서 attended=true인 수)
|
||||
// 현재는 기본적으로 주최자 + 참석자 수로 계산
|
||||
List<String> participants = meetingReader.findById(meetingId)
|
||||
.map(Meeting::getParticipants)
|
||||
.orElse(List.of());
|
||||
return participants.size() + 1; // 주최자 포함
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 회의 조회
|
||||
*/
|
||||
|
||||
+20
-93
@@ -1,7 +1,10 @@
|
||||
package com.unicorn.hgzero.meeting.infra.controller;
|
||||
|
||||
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||
import com.unicorn.hgzero.common.exception.BusinessException;
|
||||
import com.unicorn.hgzero.common.exception.ErrorCode;
|
||||
import com.unicorn.hgzero.meeting.biz.dto.MeetingDTO;
|
||||
import com.unicorn.hgzero.meeting.biz.dto.MeetingEndDTO;
|
||||
import com.unicorn.hgzero.meeting.biz.usecase.in.meeting.*;
|
||||
import com.unicorn.hgzero.meeting.infra.dto.request.CreateMeetingRequest;
|
||||
import com.unicorn.hgzero.meeting.infra.dto.request.InviteParticipantRequest;
|
||||
@@ -191,101 +194,25 @@ public class MeetingController {
|
||||
|
||||
log.info("회의 종료 요청 - meetingId: {}, userId: {}", meetingId, userId);
|
||||
|
||||
// Mock 데이터로 응답 (개발용)
|
||||
var response = createMockMeetingEndResponse(meetingId);
|
||||
|
||||
log.info("회의 종료 완료 (Mock) - meetingId: {}", meetingId);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
try {
|
||||
// 실제 비즈니스 로직 호출
|
||||
MeetingEndDTO meetingEndDTO = endMeetingUseCase.endMeeting(meetingId);
|
||||
|
||||
// DTO를 응답 객체로 변환
|
||||
MeetingEndResponse response = MeetingEndResponse.from(meetingEndDTO);
|
||||
|
||||
log.info("회의 종료 완료 - meetingId: {}", meetingId);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
|
||||
} catch (BusinessException e) {
|
||||
log.error("회의 종료 실패 - meetingId: {}, error: {}", meetingId, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("회의 종료 중 예상치 못한 오류 - meetingId: {}", meetingId, e);
|
||||
throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR, "회의 종료 처리 중 오류가 발생했습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의 종료 응답 Mock 데이터 생성
|
||||
*
|
||||
* @param meetingId 회의 ID
|
||||
* @return Mock 회의 종료 응답
|
||||
*/
|
||||
private MeetingEndResponse createMockMeetingEndResponse(String meetingId) {
|
||||
return MeetingEndResponse.builder()
|
||||
.title("Q1 전략 기획 회의")
|
||||
.participantCount(4)
|
||||
.durationMinutes(90)
|
||||
.agendaCount(3)
|
||||
.todoCount(5)
|
||||
.keywords(List.of("신제품 기획", "마케팅 전략", "예산 계획", "UI/UX 개선", "고객 분석"))
|
||||
.agendaSummaries(List.of(
|
||||
MeetingEndResponse.AgendaSummary.builder()
|
||||
.title("1. 신제품 기획 방향성")
|
||||
.aiSummaryShort("타겟 고객을 20-30대로 설정하고 UI/UX 개선에 집중하기로 결정")
|
||||
.details(MeetingEndResponse.AgendaDetails.builder()
|
||||
.discussion("신제품의 주요 타겟 고객층을 20-30대 직장인으로 설정하고 모바일 중심의 사용자 경험을 강화하는 방향으로 논의됨")
|
||||
.decisions(List.of(
|
||||
"타겟 고객: 20-30대 직장인",
|
||||
"플랫폼: 모바일 우선",
|
||||
"핵심 기능: 간편 결제, 개인화 추천"
|
||||
))
|
||||
.pending(List.of(
|
||||
"경쟁사 분석 보완 필요",
|
||||
"기술 스택 최종 검토"
|
||||
))
|
||||
.build())
|
||||
.todos(List.of(
|
||||
MeetingEndResponse.TodoSummary.builder()
|
||||
.title("시장 조사 보고서 작성")
|
||||
.build(),
|
||||
MeetingEndResponse.TodoSummary.builder()
|
||||
.title("와이어프레임 초안 제작")
|
||||
.build()
|
||||
))
|
||||
.build(),
|
||||
MeetingEndResponse.AgendaSummary.builder()
|
||||
.title("2. 마케팅 전략 수립")
|
||||
.aiSummaryShort("SNS 마케팅과 인플루언서 협업을 통한 브랜드 인지도 향상 계획")
|
||||
.details(MeetingEndResponse.AgendaDetails.builder()
|
||||
.discussion("초기 론칭 시 SNS 중심의 마케팅 전략과 마이크로 인플루언서 협업을 통한 브랜드 인지도 향상 방안 논의")
|
||||
.decisions(List.of(
|
||||
"마케팅 채널: 인스타그램, 틱톡 우선",
|
||||
"예산 배분: 인플루언서 50%, 광고 30%, 이벤트 20%",
|
||||
"론칭 시기: 2024년 2분기"
|
||||
))
|
||||
.pending(List.of(
|
||||
"인플루언서 리스트 검토",
|
||||
"마케팅 예산 최종 승인"
|
||||
))
|
||||
.build())
|
||||
.todos(List.of(
|
||||
MeetingEndResponse.TodoSummary.builder()
|
||||
.title("인플루언서 후보 리스트 작성")
|
||||
.build(),
|
||||
MeetingEndResponse.TodoSummary.builder()
|
||||
.title("마케팅 예산안 상세 작성")
|
||||
.build()
|
||||
))
|
||||
.build(),
|
||||
MeetingEndResponse.AgendaSummary.builder()
|
||||
.title("3. 프로젝트 일정 및 리소스")
|
||||
.aiSummaryShort("개발 6개월, 테스트 2개월로 총 8개월 일정 확정")
|
||||
.details(MeetingEndResponse.AgendaDetails.builder()
|
||||
.discussion("전체 프로젝트 일정을 8개월로 설정하고 개발팀 6명, 디자인팀 2명으로 팀 구성 확정")
|
||||
.decisions(List.of(
|
||||
"전체 일정: 8개월 (개발 6개월, 테스트 2개월)",
|
||||
"팀 구성: 개발 6명, 디자인 2명, PM 1명",
|
||||
"주요 마일스톤: MVP 3개월, 베타 6개월, 정식 출시 8개월"
|
||||
))
|
||||
.pending(List.of(
|
||||
"개발자 추가 채용 검토",
|
||||
"외부 업체 협업 범위 논의"
|
||||
))
|
||||
.build())
|
||||
.todos(List.of(
|
||||
MeetingEndResponse.TodoSummary.builder()
|
||||
.title("개발자 채용 공고 작성")
|
||||
.build()
|
||||
))
|
||||
.build()
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의 정보 조회
|
||||
|
||||
Reference in New Issue
Block a user