Skip to content

[Feat] 등산 기록 상세 조회에 등산 당시 기온 정보 추가#181

Merged
howooyeon merged 3 commits into
developfrom
feat/#180-hiking-temperature
Jun 3, 2026
Merged

[Feat] 등산 기록 상세 조회에 등산 당시 기온 정보 추가#181
howooyeon merged 3 commits into
developfrom
feat/#180-hiking-temperature

Conversation

@howooyeon

@howooyeon howooyeon commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🧾 요약

  • 트래킹 세션 종료 시 기상청 초단기실황 API로 산 위경도 기반 기온을 조회하여 등산 기록에 저장

🔗 이슈

✨ 변경 내용

  • hiking_records 테이블에 temperature 컬럼 추가 (Flyway V27)
  • HikingRecord 엔티티 및 HikingRecordDetailResponse 응답에 temperature 필드 추가
  • 위경도 → 기상청 격자 좌표 변환 유틸 (GridConverter) 구현
  • 기상청 초단기실황 API 연동 서비스 (WeatherService) 구현 (3초 타임아웃)
  • 트래킹 세션 종료(complete) 시 기온 조회 → 등산 기록에 저장하는 흐름 연결

✅ 확인

  • 빌드 OK
  • 테스트 OK

Summary by CodeRabbit

  • New Features
    • Hiking records now automatically capture and display temperature data from weather services, providing users with ambient temperature information for each activity.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 69408248-5425-4172-ac3f-9cefacc17e46

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#180-hiking-temperature

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@howooyeon howooyeon self-assigned this Jun 3, 2026
@howooyeon howooyeon added the enhancement New feature or request label Jun 3, 2026
@howooyeon
howooyeon merged commit ad0204b into develop Jun 3, 2026
2 of 3 checks passed
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

본 PR은 등산 기록 상세 조회 시 등산 당시의 기온 정보를 제공하기 위해 기상청 API를 연동하는 기능을 포함하고 있습니다. 트래킹 세션이 완료되는 시점에 자동으로 해당 위치의 기온을 조회하여 저장하며, 이를 위해 필요한 데이터베이스 스키마 업데이트와 외부 API 통신을 위한 설정 및 서비스 로직이 추가되었습니다.

Highlights

  • 기상청 API 연동: 트래킹 세션 종료 시 산의 위경도 정보를 기반으로 기상청 초단기실황 API를 호출하여 현재 기온을 조회하는 기능을 추가했습니다.
  • 데이터베이스 스키마 변경: 등산 기록(hiking_records) 테이블에 기온 정보를 저장하기 위한 temperature 컬럼을 추가했습니다.
  • 좌표 변환 유틸리티 구현: 위경도 좌표를 기상청 격자 좌표로 변환하기 위한 GridConverter 클래스를 신규 구현했습니다.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request integrates an external weather API to fetch and record the temperature when completing a hiking session. It introduces a WebClient configuration, a coordinate converter, a WeatherService, and updates the HikingRecord entity and database schema. Key feedback includes: avoiding external API calls within database transactions to prevent connection pool exhaustion, specifying the KST timezone for time calculations, preventing double-encoding of the API service key in WebClient, and handling inconsistent API response formats defensively to avoid ClassCastExceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +133 to +137
Mountain mountain = session.getMountain();
if (mountain.getLatitude() != null && mountain.getLongitude() != null) {
weatherService.getTemperature(mountain.getLatitude(), mountain.getLongitude())
.ifPresent(record::updateTemperature);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

[P1] DB 트랜잭션 내 외부 API 호출로 인한 커넥션 점유 위험

complete 메서드는 @Transactional 어노테이션이 적용되어 있어 데이터베이스 트랜잭션 내에서 실행됩니다. 이 안에서 weatherService.getTemperature를 호출하여 외부 기상청 API와 동기식(blocking)으로 통신하고 있습니다.

문제점

  • 외부 API 호출에 최대 3초의 타임아웃이 설정되어 있지만, 이 시간 동안 데이터베이스 커넥션을 계속 점유하게 됩니다.
  • 트래픽이 몰리거나 기상청 API의 응답이 지연될 경우, DB 커넥션 풀 고갈(Connection Pool Exhaustion)로 이어져 전체 시스템 장애를 유발할 수 있습니다.

해결 방안

기온 정보는 등산 기록의 핵심 비즈니스 로직이 아니므로, 메인 트랜잭션 외부에서 처리하는 것이 안전합니다.

  • 대안 1: 세션 완료 트랜잭션이 성공적으로 커밋된 후, 비동기 이벤트 리스너(@Async@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT))를 통해 기온을 조회하고 HikingRecord를 업데이트합니다.
  • 대안 2: complete 메서드 진입 시점에 트랜잭션 없이 기온을 먼저 조회한 후, 조회된 기온을 트랜잭션이 적용된 내부 메서드에 전달하여 저장합니다.
References
  1. 트랜잭션 경계 설정 및 외부 사이드 이펙트 호출 시 메인 비즈니스 로직에 미치는 영향(롤백 및 커넥션 점유 등)을 신중하게 검토해야 합니다. (link)

public Optional<Double> getTemperature(double latitude, double longitude) {
try {
GridConverter.Grid grid = GridConverter.toGrid(latitude, longitude);
LocalDateTime now = adjustBaseTime(LocalDateTime.now());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

[P1] 서버 시간대(Timezone) 차이로 인한 기상청 API 조회 실패 위험

LocalDateTime.now()를 사용하여 현재 시간을 가져오고 있습니다.

문제점

  • 일반적으로 클라우드 환경(AWS EC2, Docker 등)의 서버 시간대는 UTC(세계 협정시)로 설정되어 있는 경우가 많습니다.
  • 이 경우 LocalDateTime.now()는 한국 시간(KST)보다 9시간 느린 시간을 반환하므로, 기상청 API에 잘못된 base_datebase_time을 전송하게 되어 날씨 데이터를 정상적으로 조회할 수 없습니다.

해결 방안

한국 시간대(Asia/Seoul)를 명시적으로 지정하여 현재 시간을 가져오도록 수정해야 합니다.

Suggested change
LocalDateTime now = adjustBaseTime(LocalDateTime.now());
LocalDateTime now = adjustBaseTime(LocalDateTime.now(java.time.ZoneId.of("Asia/Seoul")));

Comment on lines +24 to +29
@Bean
public WebClient weatherApiWebClient() {
return WebClient.builder()
.baseUrl("https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0")
.build();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

[P2] 기상청 API Service Key 더블 인코딩(Double Encoding) 방지

공공데이터포털에서 발급받는 기상청 API의 serviceKey는 이미 인코딩된 상태로 제공되는 경우가 많습니다.

문제점

  • WebClient는 기본적으로 URI를 빌드할 때 쿼리 파라미터를 자동으로 인코딩합니다. 이로 인해 serviceKey가 이중으로 인코딩(Double Encoding)되어 기상청 API 측에서 SERVICE_KEY_IS_NOT_REGISTERED_ERROR (등록되지 않은 서비스키) 에러를 반환할 수 있습니다.

해결 방안

weatherApiWebClient 빈 등록 시 DefaultUriBuilderFactory의 인코딩 모드를 NONE으로 설정하여 이중 인코딩을 방지하는 것이 안전합니다. 기상청 API에 전달하는 다른 파라미터(날짜, 시간, 격자 좌표 등)는 단순 숫자이므로 인코딩을 하지 않아도 무방합니다.

    @Bean
    public WebClient weatherApiWebClient() {
        org.springframework.web.util.DefaultUriBuilderFactory factory = new org.springframework.web.util.DefaultUriBuilderFactory("https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0");
        factory.setEncodingMode(org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode.NONE);
        return WebClient.builder()
                .uriBuilderFactory(factory)
                .build();
    }

Comment on lines +76 to +80
Map<String, Object> items = (Map<String, Object>) body.get("items");
if (items == null) return Optional.empty();

List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item");
if (itemList == null) return Optional.empty();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

[P3] 기상청 API 응답 데이터 타입 캐스팅 예외 방지 (Defensive Programming)

기상청 API는 데이터가 없거나 에러가 발생했을 때, items 필드를 객체(Map)가 아닌 빈 문자열("")로 반환하는 일관성 없는 응답 스펙을 가지고 있습니다.

문제점

  • items가 빈 문자열("")로 반환될 경우, (Map<String, Object>) body.get("items") 코드에서 ClassCastException이 발생합니다.
  • 비록 getTemperature 메서드 전체가 try-catch로 감싸져 있어 시스템이 다운되지는 않지만, 불필요한 예외 발생 및 경고 로그가 남게 됩니다.

해결 방안

items 필드의 타입을 안전하게 검증한 후 캐스팅하도록 방어적 코드를 추가하는 것을 권장합니다.

Suggested change
Map<String, Object> items = (Map<String, Object>) body.get("items");
if (items == null) return Optional.empty();
List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item");
if (itemList == null) return Optional.empty();
Object itemsObj = body.get("items");
if (!(itemsObj instanceof Map)) return Optional.empty();
Map<String, Object> items = (Map<String, Object>) itemsObj;
List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item");
if (itemList == null) return Optional.empty();

@howooyeon
howooyeon deleted the feat/#180-hiking-temperature branch June 5, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 등산 기록 상세 조회에 등산 당시 기온 정보 추가

1 participant