Project/AutoSchedule 16

13일차 이후 ... - 그리디 배치(1차 MVP)

13일차까지 WebSocket, FullCalendar, 실시간 동기화까지 꾸준히 올렸는데그 이후로 블로그에는 업데이트를 못 올리고 있었다.그동안 개발은 계속 진행 됐고, 20일차 까지는 자동 스케줄링 엔진의 기본 구조, 그리디 배치, 점수 모델, 시간 슬롯 분할 / 연속 배치, 진행률 브로드캐스트까지는 전부 완성했다. ( + AWS EC2 배포까지 함 : http://54.206.65.33:8080 ) 21일차 기준으로는 : 캘린더 자동 업데이트 연동 완료ScheduleGenerateResponse → FullCalendar 형식 반환프런트에서 생성된 스케줄이 바로 UI에 반영됨점수 시각화는 일부만 완료백엔드: schedule.setScore() 까지 구현프런트: 점수 표시 UI 아직 없음즉, 엔진..

(12일차)13일차 - 일정 드래그/수정 반영

오늘은 FullCalendar.js를 기반으로일정을 드래그해서 수정할 수 있도록 UX를 개선했다.기존에는 일정 수정 시 폼에 직접 입력해야 했지만,이제는 드래그 & 리사이즈로 직관적으로 일정/시간을 변경할 수 있다.수정 이유 사용자 UX 개선직접 폼에 날짜·시간을 입력하는 건 번거롭고 느렸다.드래그로 바로 옮기거나 리사이즈로 시간을 늘릴 수 있다면훨씬 빠르고 직관적인 사용자 경험을 제공할 수 있다.실시간 협업 환경 개선여러 사용자가 같은 캘린더를 볼 때,한 사용자의 변경이 즉시 다른 사용자에게 반영되어야 한다.WebSocket(STOMP)을 활용해 실시간으로 동기화시켰다.비즈니스 가치일정 관리 효율 향상 → 팀 협업 생산성 향상 → 사용자 만족도 향상전체 흐름[ 사용자 드래그 ] ↓ [ 프론트엔드..

11일차 - 클라이언트 구독 테스트

AutoSchedule 프로젝트는 실시간 협업 기능이 핵심이다.캘린더 수정이나 작업 생성, 충돌 알림 등 모든 상호작용이 다른 사용자 화면에 즉시 반영되어야 한다.그런데 기존에는 WebSocket이 실제로 잘 작동하는지 확인할 방법이 없다는 문제가 있었다. JWT 인증 실패Rate Limit 초과Origin 검증 실패토큰 만료네트워크 끊김서버 재시작 중 연결 해제이런 문제들이 발생해도 브라우저 콘솔에 로그만 찍힐 뿐,실제로 클라이언트가 정상적으로 구독·수신을 하고 있는지 눈으로 확인하기 어려운 구조였다. 코드를 보면 실제로 아래와 같은 위험 요소가 있었다 : 구독 실패 시 조용히 실패메시지가 전송/수신됐는지 확인 불가여러 토픽 구독 시 중복 메시지 위험예외 상황에 대한 대응 부족이런 문제들을 해결하기 ..

10일차 - SlotLock 구현

오늘은 동시에 같은 슬롯을 두번 건드리는 일을 막기 위해 TTL 기반 슬롯 락 인프라를 구현했다.락 획득 / 연장 / 해제 / 로직과 만료 청소 스케줄러를 붙였고,Mockito 기반으로 서비스 로직을 단위 테스트해보았다. TTL 기반 락을 선택한 이유TTL(Time-To-Live)이란 ?락이 자동으로 해제되는 시각을 함께 저장해 두고, 그 시간이 지나면 다른 주체가 락을 가져갈 수 있게 하는 방식이다.예를 들어 tryLock("slot-1", userId, Duration.ofSeconds(30))은 30초 후 자동 만료되어 타 사용자가 재획득 가능하다는 뜻. 다른 방식들과 비교1) DB 플래그 방식(수동 해제)단순히 locked = true / false 만 관리하는 방식이다. UPDATE slot S..

9일차 - 실시간 스케줄 브로드캐스트 구현

어제는 WebSocket/STOMP 기반의 실시간 협업 인프라를 구축했다.오늘은 그 연장선으로, AI 스케줄 최적화 결과를 실시간으로 브로드캐스트하는 기능을 구현했다.기존 구조에서는 진행률만 실시간으로 전송되고, 최종 스케줄 결과는 프론트가 별도로 REST API를 호출해야 했다.이 방식의 문제는 다음과 같았다.AI 최적화 완료 시점을 프론트가 알 수 없음API 응답 타이밍 차이로 화면이 늦게 갱신됨다른 도메인(Task, Calendar, Notification)과의 실시간 흐름이 불일치결국 “진행률은 실시간인데 결과는 수동 갱신”이라는 어색한 구조가 됐다. 그래서 오늘은 최종 스케줄까지 자동으로 push하는 브로드캐스터 로직을 추가했다.1. 기존 구조기존 ScheduleOptimizationServic..

8.5일차 - Spring WebSocket 보안 3단계 추가

어제는 실시간 일정 편집, 자동 스케줄 배포, 협업 알림을 위해 WebSocket을 구현했다.오늘은 이 WebSocket 통신의 보안성과 안정성을 높이기 위해, 세 가지 단계의 보안 인터셉터를 추가했다.핸드셰이크 → 인증 → 트래픽 제어까지, STOMP 전 구간을 검증하는 구조다.1. StrictHandshakeInterceptor – Origin / TLS 검사WebSocket 연결 직전에 실행되는 핸드셰이크 인터셉터로, 다음 두 가지를 검사한다.허용 Origin 확인: 외부 도메인의 비정상 요청 차단TLS 강제 여부 검사: 배포 환경에서는 wss:// 만 허용이 값들은 모두 application.properties 의 app.websocket.allowed-origins, app.websocket.r..

8일차 - WebSocket 환경 세팅

오늘은 프로젝트에 실시간 협업 기능을 추가하기 위해 WebSocket과 STOMP를 구현했다.이 프로젝트에서는 여러 사용자가 동시에 작업 및 일정을 관리하기 때문에, 서버 - 클라이언트 간 양방향 통신이 필수적이다. 이번에 WebSocket을 도입한 이유는 다음과 같다. 실시간 일정 편집: 팀원이 캘린더에서 회의, 개인일정, 작업을 추가하거나 이동, 삭제할 때다른 사람의 화면에서도 즉시 반영되어야 한다.자동 스케줄 결과 배포: AI가 최적화한 스케줄을 계산 완료 즉시,모든 클라이언트에 결과와 세부 일정을 push해야 한다.충돌 경고 / 진행률 표시: 스케줄 최적화 중 충돌 감지, 진행률, 추천 변경안 같은 중간 상태를실시간으로 띄우려면 WebSocket 스트리밍이 가장 효율적이다.협업 알림: 특정 작업..

7일차 - Swagger or Postman API 문서 작성

Swagger 경험이 더 많기도 하고, 프로젝트 초반에 이미 Swagger 의존성을 추가해두었기 때문에 이번에는 Swagger 기반으로 API 문서 작성을 진행하였다.오늘 진행한 작업은 다음과 같다.Swagger 의존성 추가SecurityConfig에서 Swagger 경로 허용Controller 메서드 및 DTO에 @Operation, @Schema 등 문서화 어노테이션 추가1. 의존성 추가먼저 build.gradle 에 다음과 같이 Swagger 의존성을 추가한다. // Swagger / OpenAPI 문서화 implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' 참고로, Spring Boot 3.x를 사용하는 경우 → s..

6일차 - Task/CalendarEvent API 검증 & 캘린더 통합

오늘은 Task / CalendarEvent API에 검증 로직을 추가하고,프론트엔드에서 작업(Task)을 캘린더에 표시하는 기능까지 통합했다.팀별 색상과 우선순위에 따른 색 진하기로 가독성도 개선했다. 추가한 기능작업 추가하기 (모달 + API 연동)우선순위 지정(1~5) - (지금은 사용자 지정, 정책 고민 예정)팀별 색상 지정 / 우선순위 별 색 진하기캘린더에 작업 표시(마감일 기준)데이터 무결성 검증 강화(DTO + Service)1) 백엔드 - 검증 로직(DTO + Service)컨트롤러 이전 단계에서 "형식"을 걸러내고, 서비스에서 "의미/도메인 규칙"을 보장해 무결성과 가독성을 동시에 확보하기 위해두 단계를 수정했다. 1-1. DTO 레벨 검증 (Jakarta Validation) - 형식/..

5일차 - Team / TeamMember API 기능 구현

오늘은 팀(Team) 및 팀 구성원(TeamMember) 관련 기능을 구현했다.팀 생성, 초대, 조회 기능까지 백엔드 API로 완성했으며,기능 테스트를 위해 프론트엔드에도 간단한 테스트 버튼을 추가했다.- 백엔드1) 팀 생성# TeamContoroller.java@PostMapping public ResponseEntity createTeam(@Valid @RequestBody TeamCreateRequest request) { TeamResponse response = teamService.createTeam(request); return ResponseEntity.status(HttpStatus.CREATED).body(response); } # TeamSe..