[Rookies 개발 4기][개발]39. 과거 날짜 일기 생성 시 위치 기록 복원 및 날씨 정보 연동 로직 고도화, 다이어리 서비스 비즈니스 로직 분리 및 오케스트레이터 구조 도입, AI 일기 생성 로직 고도화 및 멀티모달 프롬프트 최적화, 리캡 생성 알림 및 코인 적립 연동

Seolhxx·2026년 1월 5일

SK 쉴더스

목록 보기
43/47

[🤍 개발 기록 2026.01.05 🤍]

과거 날짜 위치 복원 + 과거 날씨(ASOS) 연동, 리캡/일기 알림·코인 보상까지: “기록 경험”을 완성하는 흐름 만들기

오늘 작업은 기능을 몇 개 추가한 수준이 아니라,
과거 기록을 “그날의 컨텍스트(위치/날씨)”로 복원하고,
생성 완료 후 알림과 보상(코인) 으로 사용자 경험을 닫는(Closing) 작업이었다.
(Front: 위치 전송 분기/타임아웃/알림 라우팅, Back: 위치 복원/ASOS/서비스 분리/알림/코인)


1. 문제 정의: “과거 일기인데 현재 위치가 박히는 문제”

AI 일기/미리보기는 사용자가 과거 날짜를 선택해도, 클라이언트가 현재 GPS를 그대로 전송하면서 과거 기록에 현재 위치가 덮이는 문제가 발생할 수 있다.
또한 그날의 날씨는 실시간 예보가 아니라 과거 기상 기록(ASOS) 을 써야 컨텍스트가 맞는다.

따라서 오늘 목표는 다음 두 가지였다.

  • 과거 날짜 일기 작성/생성 시 해당 날짜의 위치를 DB에서 복원하고 UI에 반영
  • 날짜에 따라 실시간 예보 vs 과거 기록(ASOS) 를 자동 분기해 날씨를 정확히 적용

2. Front: 과거 날짜는 GPS를 보내지 않는다 (위치 복원 허용)

2.1 과거 날짜 GPS 전송 로직 수정 (DiaryUploadPage.tsx)

핵심은 “과거 날짜이면 클라이언트 GPS를 보내지 않고”,
백엔드가 DB 기준으로 위치를 찾아 반영할 수 있도록 요청 모델을 분기한 것이다.

  • 과거 날짜 선택 시 GPS 좌표 전송하지 않도록 변경
  • isPastDate 플래그로 조건부 위치 전송
  • 의도: 클라이언트가 현재 위치로 과거 기록을 오염시키지 않기

2.2 백엔드 응답 위치 정보 우선 사용

사용자가 선택한 날짜가 과거인지 여부에 따라, 프론트에서 최종 위치를 결정하는 로직을 추가했다.

  • 과거 날짜: 백엔드가 반환한 latitude, longitude를 우선 사용
  • 오늘 날짜: GPS 위치를 우선 사용
  • finalLocationCoords, finalLocationName로 UI 반영 기준을 단일화

결과적으로 UI는 “오늘은 GPS”, “과거는 DB 복원”이라는 정책이 명확해지고,
화면/지도/날씨 로직이 한 기준으로 움직이게 된다.



2.3 전역 타임아웃 증가 (http-client.ts)

AI 일기 생성, 리캡 생성 등은 요청 시간이 길 수 있어 기본 10초 타임아웃에서 끊기는 문제가 있었다.

  • 전역 HTTP timeout: 10초 → 60초
  • 목적: ECONNABORTED 같은 타임아웃 에러 방지

2.4 디버깅 로그 추가

날짜 분기와 좌표 적용은 오류가 나면 체감이 큰 영역이라, 판단 근거를 로그로 남겼다.

  • 과거/오늘 날짜 구분 로그
  • GPS 전송 여부 로그
  • 최종 위치 정보 로그

3. Back: “과거 날짜면 DB 위치를 최우선” + 과거 날씨(ASOS) 자동 연동

3.1 위치 복원 로직 고도화 (DiaryServiceImpl)

백엔드에서 “과거 날짜”일 경우 위치 우선순위를 명확히 정의했다.

  • 과거 날짜 우선순위: 오늘 이전이면 클라이언트 좌표보다 DB(walk_routes) 를 최우선
  • 폴백: 해당 날짜 기록이 없으면 “오늘 대표 위치”를 조회해 기본값 제공
    → 완전히 빈 장소가 되지 않도록 UX를 방어
  • 상태 동기화: 복원된 좌표를 AiDiaryResponse에 포함해 프론트 UI가 즉시 갱신 가능
  • 주소 보정: DB에서 좌표를 복원한 경우 Kakao API로 실제 주소명을 자동 설정(행정동(H) 우선)


3.2 날씨 로직 통합 (ExternalApiService)

날씨는 “날짜”에 따라 호출 API가 달라야 한다.

  • getWeatherInfo에서 날짜 기준으로 자동 분기:
    • 오늘/미래: 실시간 예보(초단기예보)
    • 과거: ASOS 일자료(과거 기상 기록)
  • 좌표 기준 가장 가까운 관측소를 찾아 실제 기록을 가져오도록 구성

이 방식은 “사진 기반 추정”보다 신뢰성이 높고,
과거 기록을 ‘그날의 날씨’로 맞출 수 있다는 점에서 데이터 품질이 상승한다.


3.3 데이터 영속성 강화 (LocationServiceImpl)

과거 위치 복원이 “일회성”이면 재사용 가치가 낮다.
그래서 일기 저장 시 해당 날짜/장소를 walk_routes에 기록하도록 보강해,
같은 날짜에 추가 일기를 작성할 때 동일 위치를 공유할 수 있게 했다.


4. 서비스 구조 개선: DiaryServiceImpl을 오케스트레이터로 전환

DiaryServiceImpl에 집중되어 있던 비대한 로직을 관심사별로 분리하고,
상위에서 흐름만 제어하는 오케스트레이터 패턴으로 구조를 정리했다.

  • DiaryAiService: AI 분석/프롬프트 실행
  • DiaryVectorService: Milvus 동기화/비동기 저장
  • DiaryMediaService: 이미지 업로드(Archive) + MongoDB 메타데이터
  • ExternalApiService: Kakao 주소 변환 + 날씨 API 분기

효과: 변경이 자주 생기는 영역(외부 API, AI, 미디어)을 분리해서
추후 유지보수와 테스트 작성 난이도를 낮추는 방향으로 정리했다.


5. 알림 연동: 리캡/일기 생성 완료를 사용자에게 “닫아주기”

5.1 Front: 알림 API 연동 및 라우팅 개선 (Closes #115)

  • createNotification API 함수 추가 및 요청 DTO 정의
  • 일기 저장 완료 시 DIARY 타입 알림 전송 (DiaryStylePage, AiDiaryPage)
  • 알림 클릭 라우팅 개선
    • DIARY/diary/{targetId}
    • RECAP/recap/{targetId}
  • 불필요한 소셜 피드 공유 알림 제거

핵심은 “알림이 와도 사용자가 어디로 가야 하는지”가 명확해야 한다는 점이었다.
캘린더 같은 우회 경로 대신 상세 페이지로 바로 이동하도록 정리했다.


5.2 Back: 리캡 생성 알림 전송(실패해도 본 트랜잭션 유지)

  • NotificationClient(Feign)로 유저 서비스 알림 API 호출
  • RecapServiceImpl.createAiRecap()에서 자동/수동 생성 모두 알림 지원
  • try-catch 처리: 알림 실패해도 리캡 생성은 정상 처리
  • 디버깅을 위한 상세 로그 기록 + 중복 알림 제거



6. 코인 보상: 리캡 생성 성공 시 30코인 적립

사용자 행동을 “완료 경험”으로 만드는 방법 중 하나가 보상 체계다.
리캡 생성은 사용자 입장에서 비용이 큰 행동(기억/정리/회고)이므로 일기보다 보상을 높게 설정했다.

  • 리캡 생성 성공 시 30코인 자동 적립
  • UserClient(Feign)로 유저 서비스 earnCoin 호출
  • 자동 예약 리캡 / 수동 생성 리캡 모두 적용
  • 보상 실패해도 리캡 생성은 정상 처리(try-catch)
  • 알림에 코인 정보 포함(coin: 30L)하여 사용자에게 즉시 피드백 제공


7. 테스트 결과 요약

  • 과거 날짜 선택 → GPS 전송 안 함 확인
  • 오늘 날짜 선택 → GPS 전송 확인
  • 백엔드 응답 위치 정보 정상 반영 확인
  • 과거 위치 존재/미존재 시나리오 UI 확인(없을 때는 AI 추정 장소로 표시 후 수정 유도)
  • 타임아웃 에러 해결 확인(60초)
  • 일기/리캡 생성 후 알림 전송 및 클릭 라우팅(/diary/:id, /recap/:id) 확인
  • 리캡 생성 시 30 코인 적립 및 알림 문구 확인

8. 회고

오늘 작업에서 가장 크게 느낀 점은 “과거 기록” 기능은 단순히 날짜만 바꾸는 게 아니라, 그날의 컨텍스트(위치/날씨)까지 같이 복원되어야 완성된 경험이 된다는 것이었다. 프론트에서 과거 날짜에 GPS를 보내지 않도록 정책을 분기한 것은 작은 변경처럼 보이지만, 데이터 오염을 막는 핵심 안전장치가 됐다. 백엔드에서 DB 위치를 최우선으로 사용하고 폴백을 두면서, “정확성”과 “사용자 경험” 사이의 균형을 실제로 설계해볼 수 있었다. 특히 날씨는 실시간 예보와 과거 기록(ASOS)을 분기해야 한다는 점에서, 시간 개념이 들어간 외부 API 설계가 생각보다 복잡하다는 걸 배웠다. 서비스 분리(오케스트레이터 패턴) 과정에서는 기능이 늘어날수록 한 클래스에 쌓이는 로직이 유지보수성을 빠르게 무너뜨린다는 걸 체감했고, 책임을 나누는 것 자체가 리팩토링의 핵심임을 다시 확인했다. 알림 연동은 “기능 완료 후 사용자에게 닫아주는 경험”을 만드는 작업이었고, 클릭 라우팅을 직접 상세로 연결하니 알림의 가치가 올라갔다. 코인 보상은 단순한 포인트 지급이 아니라, 사용자의 행동을 서비스 흐름에 정착시키는 장치라는 점을 배우게 됐다. 동시에 알림/코인처럼 외부 서비스에 의존하는 기능은 실패해도 본 트랜잭션을 망치지 않도록 설계해야 한다는 원칙을 다시 적용했다. 전역 타임아웃을 60초로 올리면서 “긴 작업”을 다루는 서비스는 네트워크 정책도 제품의 일부라는 걸 느꼈다. 전체적으로는 기능 하나를 붙이는 것보다, 데이터 정합성·예외 처리·UX 피드백 루프를 함께 닫는 게 더 중요한 작업이라는 걸 경험한 하루였다.

0개의 댓글