-
Notifications
You must be signed in to change notification settings - Fork 1
[#18] Feat: 항공권 조회 API구현 #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
워크스루Amadeus 항공사 API를 통합하여 항공편 데이터 조회 기능을 추가합니다. 항공편 엔티티, 리포지토리, 컨트롤러 및 서비스를 구현하고, API 응답을 변환하는 컨버터를 제공합니다. 주기적 스케줄링과 보안 설정도 함께 업데이트됩니다. 변경사항
제안 검토자
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (12)
src/main/java/com/example/triptalk/domain/tripPlan/converter/AmadeusConverter.java (2)
20-24: 사용되지 않는 파라미터 제거 권장
tempId파라미터가 사용되지 않으며 Javadoc에서도 "사용되지 않음"으로 명시되어 있습니다. 불필요한 파라미터는 제거하는 것이 좋습니다.public static Flight toFlight( AmadeusResponse.FlightOffer flightOffer, - Boolean isOutbound, - Long tempId + Boolean isOutbound ) {
71-88: 하드코딩된 환율은 시간이 지나면 정확도가 떨어집니다환율이 코드에 하드코딩되어 있어 실제 환율과 차이가 발생할 수 있습니다. PR 설명에 따르면 주간 업데이트가 진행되므로, 환율 API를 활용하거나 설정 파일에서 환율을 관리하는 방안을 고려해 주세요.
환율 API 통합 또는 설정 기반 환율 관리 구현을 도와드릴까요?
src/main/java/com/example/triptalk/domain/tripPlan/service/FlightServiceImpl.java (1)
23-23: PAGE_SIZE 설정 외부화 권장페이지 크기를
application.yml에서 설정할 수 있도록@Value어노테이션 사용을 고려해 보세요. 운영 중 튜닝이 용이해집니다.src/main/java/com/example/triptalk/domain/tripPlan/entity/Flight.java (2)
9-15: 엔티티에 @Setter 사용 검토JPA 엔티티에
@Setter를 사용하면 의도치 않은 필드 변경이 가능해집니다. 현재 코드에서는@Builder로 생성하고 업데이트가 없으므로,@Setter제거를 권장합니다.@Builder @Getter -@Setter @NoArgsConstructor @AllArgsConstructor @Entity public class Flight extends BaseEntity {
14-40: 커서 기반 조회를 위한 인덱스 추가 권장커서 기반 페이지네이션에서
id기준 조회가 빈번할 것으로 예상됩니다. Primary Key는 자동으로 인덱싱되지만, 복합 조건 조회가 추가될 경우 인덱스 전략을 검토해 주세요.src/main/java/com/example/triptalk/domain/tripPlan/service/AmadeusService.java (2)
30-34: 토큰 갱신 시 이중 검사 잠금(Double-Checked Locking) 적용 권장현재 구현에서는 여러 스레드가 동시에 토큰 만료를 감지하면 중복 토큰 발급 요청이 발생할 수 있습니다.
+ private final Object tokenLock = new Object(); + private String getAccessToken() { // 토큰이 유효하면 재사용 if (accessToken != null && System.currentTimeMillis() < tokenExpiryTime) { return accessToken; } + + synchronized (tokenLock) { + // Double-check after acquiring lock + if (accessToken != null && System.currentTimeMillis() < tokenExpiryTime) { + return accessToken; + } + // ... token fetch logic ... + }
97-102: URL 파라미터 인코딩 누락쿼리 파라미터를 직접 문자열로 연결하면 특수 문자 인코딩이 누락될 수 있습니다.
UriComponentsBuilder사용을 권장합니다.- String dateStr = departureDate.format(DateTimeFormatter.ISO_LOCAL_DATE); - String fullUrl = String.format( - "%s?originLocationCode=%s&destinationLocationCode=%s&departureDate=%s&adults=%d&max=%d", - url, originLocationCode, destinationLocationCode, dateStr, adults, max - ); + String fullUrl = UriComponentsBuilder.fromHttpUrl(url) + .queryParam("originLocationCode", originLocationCode) + .queryParam("destinationLocationCode", destinationLocationCode) + .queryParam("departureDate", departureDate.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .queryParam("adults", adults) + .queryParam("max", max) + .toUriString();src/main/java/com/example/triptalk/domain/tripPlan/dto/AmadeusResponse.java (5)
5-5: 와일드카드 임포트는 필요 시 개별 임포트로 정리하는 것을 검토해 보세요.
import lombok.*;형태는 일부 정적 분석/컨벤션 설정에서 경고가 날 수 있습니다. 사용하는 애노테이션만 명시적으로 임포트하면 가독성과 도구 호환성이 조금 더 좋아집니다.-import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter;
26-35: FlightOffersResponse에서 data/meta/dictionaries 분리가 실용적인 선택입니다.현재 사용 중인
data만 구체 타입으로 두고,meta·dictionaries를Object로 둔 것은 필요 이상으로 타입을 조이지 않으면서도 응답 전체를 보존하려는 용도로는 적절해 보입니다. 다만 이 필드들을 비즈니스 로직에서 적극 활용하게 되면, 별도 DTO 타입으로 정의해 두면 컴파일 타임 검증을 더 받을 수 있습니다.
37-58: itineraries/segments를 배열로 둔 설계는 괜찮지만, 컬렉션 활용을 고려해 볼 수 있습니다.현재 JSON 구조와 1:1 매핑이라는 관점에서는 배열(
Itinerary[],Segment[]) 사용이 충분히 타당합니다. 다만 이후 스트림/컬렉션 연산을 많이 하게 된다면 DTO에서 바로List<Itinerary>,List<Segment>를 사용하거나, 배열을List로 감싸는 헬퍼 메서드를 추가하는 것도 선택지로 두시면 좋겠습니다.
74-92: 출발/도착 시간 필드를 String으로 유지하는 선택은 단순 매핑에는 적절합니다.Amadeus 원본 응답을 그대로 보존하는 DTO 용도에는
at을 String으로 두는 것이 간단하고 이해하기 좋습니다. 다만 시간대·차이 계산 같은 비즈니스 로직이 많아지면, 도메인 레벨로 변환할 때OffsetDateTime/ZonedDateTime등 JDK 날짜 타입으로 변환해 두면 런타임 오류를 줄이는 데 도움이 됩니다.
103-112: Price를 String으로 보존하는 설계는 원본 유지에는 좋고, 계산은 별도 도메인 모델에서 처리하는 방식이 어울립니다.환율 변환·합산 등 금액 연산이 잦다면, 이 DTO를 바로 쓰기보다는 이후 변환 레이어에서
currency/total/base를BigDecimal기반의 값 객체로 매핑해 두는 편이 부동소수점/파싱 오류를 줄이기에 더 안전합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/main/java/com/example/triptalk/TriptalkApplication.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/controller/FlightController.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/converter/AmadeusConverter.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/converter/FlightConverter.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/dto/AmadeusResponse.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/dto/FlightResponse.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/Flight.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/repository/FlightRepository.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/scheduler/FlightScheduler.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/service/AmadeusService.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/service/FlightService.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/service/FlightServiceImpl.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/util/AirportNameMapper.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/util/CountryImageMapper.java(1 hunks)src/main/java/com/example/triptalk/global/config/AmadeusProperties.java(1 hunks)src/main/java/com/example/triptalk/global/config/SecurityConfig.java(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/example/triptalk/domain/tripPlan/service/FlightService.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/dto/FlightResponse.java (1)
FlightResponse(12-71)
src/main/java/com/example/triptalk/domain/tripPlan/service/AmadeusService.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/dto/AmadeusResponse.java (1)
AmadeusResponse(9-113)
src/main/java/com/example/triptalk/domain/tripPlan/scheduler/FlightScheduler.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/converter/AmadeusConverter.java (1)
AmadeusConverter(11-133)
src/main/java/com/example/triptalk/domain/tripPlan/converter/AmadeusConverter.java (2)
src/main/java/com/example/triptalk/domain/tripPlan/dto/AmadeusResponse.java (1)
AmadeusResponse(9-113)src/main/java/com/example/triptalk/domain/tripPlan/util/CountryImageMapper.java (1)
CountryImageMapper(12-239)
src/main/java/com/example/triptalk/domain/tripPlan/controller/FlightController.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/dto/FlightResponse.java (1)
FlightResponse(12-71)
src/main/java/com/example/triptalk/domain/tripPlan/converter/FlightConverter.java (2)
src/main/java/com/example/triptalk/domain/tripPlan/dto/FlightResponse.java (1)
FlightResponse(12-71)src/main/java/com/example/triptalk/domain/tripPlan/util/AirportNameMapper.java (1)
AirportNameMapper(6-138)
🔇 Additional comments (17)
src/main/java/com/example/triptalk/TriptalkApplication.java (1)
6-9: LGTM!스케줄링 지원을 활성화하는 변경사항이 올바르게 구현되었습니다. FlightScheduler의 스케줄링 기능이 정상적으로 동작할 수 있습니다.
src/main/java/com/example/triptalk/domain/tripPlan/service/FlightService.java (1)
1-13: LGTM!인터페이스 설계가 명확하고 Javadoc 문서화가 잘 되어 있습니다. 커서 기반 무한 스크롤 방식의 페이징 처리가 적절합니다.
src/main/java/com/example/triptalk/global/config/SecurityConfig.java (2)
44-45: LGTM!항공권 검색 API를 비회원에게도 공개하는 설정이 적절합니다.
47-53: LGTM!Swagger UI 접근 경로가 포괄적으로 잘 구성되어 있습니다.
src/main/java/com/example/triptalk/global/config/AmadeusProperties.java (1)
1-17: 테스트 환경 URL 사용을 확인하세요.기본값으로 Amadeus 테스트 API 엔드포인트(
https://test.api.amadeus.com)를 사용하고 있습니다. 개발 환경에서는 적절하지만, 프로덕션 배포 시 실제 API 엔드포인트로 변경이 필요합니다.
application.properties또는application-prod.properties에서 다음과 같이 오버라이드할 수 있는지 확인하세요:amadeus.base-url=https://api.amadeus.comsrc/main/java/com/example/triptalk/domain/tripPlan/controller/FlightController.java (1)
1-62: LGTM!컨트롤러 구현이 우수합니다. Swagger 문서화가 매우 상세하고, 서비스 레이어로의 위임이 올바르게 처리되어 있습니다. 커서 기반 페이징 방식도 적절합니다.
src/main/java/com/example/triptalk/domain/tripPlan/util/CountryImageMapper.java (1)
221-238: LGTM!공항 코드를 이미지 URL로 매핑하는 로직이 잘 구현되어 있습니다. null/empty 체크와 기본 이미지 폴백 처리가 적절합니다.
src/main/java/com/example/triptalk/domain/tripPlan/util/AirportNameMapper.java (1)
1-139: LGTM!공항 코드를 한국어 명칭으로 매핑하는 유틸리티가 잘 구현되어 있습니다. 주요 공항들을 포괄적으로 커버하고 있으며, 매핑되지 않은 코드에 대한 폴백 처리도 적절합니다.
src/main/java/com/example/triptalk/domain/tripPlan/repository/FlightRepository.java (1)
1-26: LGTM!커서 기반 페이징 쿼리가 올바르게 구현되어 있습니다.
Slice를 사용하여 불필요한 count 쿼리를 피하고, NULL 처리로 첫 페이지 조회가 가능합니다. 성능을 고려한 좋은 설계입니다.src/main/java/com/example/triptalk/domain/tripPlan/converter/FlightConverter.java (2)
15-27: LGTM!Flight 엔티티를 DTO로 변환하는 로직이 올바르게 구현되었습니다. AirportNameMapper를 통한 IATA 코드 → 한국어명 변환도 적절합니다.
38-40: The project'sbuild.gradleexplicitly configureslanguageVersion = JavaLanguageVersion.of(21), confirming that Java 21 is the target version. The use ofList.getLast()is fully compatible and appropriate for this project. No changes needed.Likely an incorrect or invalid review comment.
src/main/java/com/example/triptalk/domain/tripPlan/dto/FlightResponse.java (2)
14-47: LGTM!FlightDTO가 잘 구성되었습니다. Swagger Schema 어노테이션을 통한 API 문서화와 Lombok을 활용한 보일러플레이트 코드 감소가 적절합니다.
49-70: LGTM!커서 기반 페이지네이션을 위한 FlightListResultDTO가 적절하게 설계되었습니다.
nextCursorId,hasNext,isFirst필드를 통해 무한 스크롤 구현에 필요한 정보를 제공합니다.src/main/java/com/example/triptalk/domain/tripPlan/scheduler/FlightScheduler.java (1)
168-179: 중첩 트랜잭션 동작 확인 필요
initialLoadFlights()가@Transactional로 선언되었고 내부에서updateFlights()를 호출합니다. 동일 클래스 내 메서드 호출은 프록시를 거치지 않아updateFlights()의@Transactional이 적용되지 않을 수 있습니다.
updateFlights()를 별도 서비스로 분리하거나,initialLoadFlights()에서@Transactional을 제거하는 방안을 검토해 주세요.src/main/java/com/example/triptalk/domain/tripPlan/service/FlightServiceImpl.java (1)
15-34: LGTM!서비스 구현이 깔끔합니다.
@Transactional(readOnly = true)를 통한 읽기 전용 트랜잭션 설정과 커서 기반 페이지네이션 로직이 적절합니다.src/main/java/com/example/triptalk/domain/tripPlan/dto/AmadeusResponse.java (2)
1-9: Amadeus API 응답을 한 DTO로 묶은 구조가 명확합니다.외부 API 전용 DTO를
AmadeusResponse내부의 중첩 static 클래스로 정리해서, 도메인 모델과 책임이 잘 분리되어 있고 이후 컨버터/서비스에서 사용하기에도 깔끔해 보입니다.
11-24: AccessToken DTO가 토큰 응답 형식을 잘 반영하고 있습니다.JSON의 snake_case 필드를 자바 필드와 명시적으로 매핑해 두어서, 토큰 응답 스펙을 추적·유지보수하기에 충분히 읽기 좋은 형태입니다.
| // 첫 번째 여정 정보 추출 (출발편 또는 귀환편) | ||
| AmadeusResponse.Itinerary itinerary = flightOffer.getItineraries()[isOutbound ? 0 : 1]; | ||
| AmadeusResponse.Segment firstSegment = itinerary.getSegments()[0]; | ||
| AmadeusResponse.Segment lastSegment = itinerary.getSegments()[itinerary.getSegments().length - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배열 접근 시 예외 발생 가능성
itineraries 배열이 비어있거나 isOutbound가 false일 때 itinerary가 1개만 있으면 ArrayIndexOutOfBoundsException이 발생합니다. 또한 segments 배열이 비어있을 경우에도 예외가 발생할 수 있습니다.
+ if (flightOffer.getItineraries() == null || flightOffer.getItineraries().length == 0) {
+ throw new IllegalArgumentException("Flight offer has no itineraries");
+ }
+ int itineraryIndex = isOutbound ? 0 : Math.min(1, flightOffer.getItineraries().length - 1);
// 첫 번째 여정 정보 추출 (출발편 또는 귀환편)
- AmadeusResponse.Itinerary itinerary = flightOffer.getItineraries()[isOutbound ? 0 : 1];
+ AmadeusResponse.Itinerary itinerary = flightOffer.getItineraries()[itineraryIndex];
+ if (itinerary.getSegments() == null || itinerary.getSegments().length == 0) {
+ throw new IllegalArgumentException("Itinerary has no segments");
+ }
AmadeusResponse.Segment firstSegment = itinerary.getSegments()[0];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 첫 번째 여정 정보 추출 (출발편 또는 귀환편) | |
| AmadeusResponse.Itinerary itinerary = flightOffer.getItineraries()[isOutbound ? 0 : 1]; | |
| AmadeusResponse.Segment firstSegment = itinerary.getSegments()[0]; | |
| AmadeusResponse.Segment lastSegment = itinerary.getSegments()[itinerary.getSegments().length - 1]; | |
| if (flightOffer.getItineraries() == null || flightOffer.getItineraries().length == 0) { | |
| throw new IllegalArgumentException("Flight offer has no itineraries"); | |
| } | |
| int itineraryIndex = isOutbound ? 0 : Math.min(1, flightOffer.getItineraries().length - 1); | |
| // 첫 번째 여정 정보 추출 (출발편 또는 귀환편) | |
| AmadeusResponse.Itinerary itinerary = flightOffer.getItineraries()[itineraryIndex]; | |
| if (itinerary.getSegments() == null || itinerary.getSegments().length == 0) { | |
| throw new IllegalArgumentException("Itinerary has no segments"); | |
| } | |
| AmadeusResponse.Segment firstSegment = itinerary.getSegments()[0]; | |
| AmadeusResponse.Segment lastSegment = itinerary.getSegments()[itinerary.getSegments().length - 1]; |
| try { | ||
| // 기존 데이터 모두 삭제 | ||
| flightRepository.deleteAll(); | ||
| log.info("기존 항공권 데이터 삭제 완료"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
데이터 일관성 위험: 삭제 후 저장 실패 시 데이터 손실
deleteAll() 후 API 호출이나 saveAll()이 실패하면 항공권 데이터가 모두 삭제된 상태로 남을 수 있습니다. 새 데이터를 먼저 임시 저장하거나, 실패 시 롤백 전략을 고려해 주세요.
대안으로 soft delete 또는 버전 기반 교체 패턴을 사용할 수 있습니다:
@Transactional
public void updateFlights() {
log.info("=== 항공권 데이터 업데이트 시작 ===");
try {
- // 기존 데이터 모두 삭제
- flightRepository.deleteAll();
- log.info("기존 항공권 데이터 삭제 완료");
-
// 7일 후 출발 날짜
LocalDate departureDate = LocalDate.now().plusDays(7);
List<Flight> allFlights = new ArrayList<>();
// ... fetch logic ...
// 일괄 저장
- flightRepository.saveAll(allFlights);
+ if (!allFlights.isEmpty()) {
+ flightRepository.deleteAll();
+ flightRepository.saveAll(allFlights);
+ }
log.info("=== 항공권 데이터 업데이트 완료: 총 {}건 ===", allFlights.size());Committable suggestion skipped: line range outside the PR's diff.
| // API Rate Limit 방지를 위해 대기 | ||
| Thread.sleep(1000); | ||
|
|
||
| } catch (Exception e) { | ||
| log.error("노선 조회 실패: {} → {}, 에러: {}", | ||
| route.originName, route.destinationName, e.getMessage()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InterruptedException 처리 개선 필요
Thread.sleep()이 발생시키는 InterruptedException이 일반 Exception으로 처리되어 스레드의 인터럽트 상태가 손실됩니다.
// API Rate Limit 방지를 위해 대기
- Thread.sleep(1000);
+ Thread.sleep(1000);
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ log.warn("항공권 업데이트 중 인터럽트 발생");
+ break;
} catch (Exception e) {
log.error("노선 조회 실패: {} → {}, 에러: {}",
route.originName, route.destinationName, e.getMessage());
}🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/scheduler/FlightScheduler.java
around lines 146 to 152, the current catch block treats InterruptedException
from Thread.sleep as a generic Exception and thus loses the thread's interrupt
status; change the code to catch InterruptedException separately, restore the
interrupt status with Thread.currentThread().interrupt(), log an appropriate
message, and either return/propagate if the task should stop; keep the generic
Exception catch for other errors and log them as before.
| private final AmadeusProperties amadeusProperties; | ||
| private final RestTemplate restTemplate = new RestTemplate(); | ||
|
|
||
| private String accessToken; | ||
| private long tokenExpiryTime; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스레드 안전성 문제 및 RestTemplate 주입 권장
accessToken과tokenExpiryTime이 동기화 없이 여러 스레드에서 접근될 수 있어 경쟁 조건이 발생할 수 있습니다.RestTemplate을 직접 인스턴스화하면 Spring의 설정(타임아웃, 인터셉터 등)을 적용할 수 없습니다.
@Slf4j
@Service
@RequiredArgsConstructor
public class AmadeusService {
private final AmadeusProperties amadeusProperties;
- private final RestTemplate restTemplate = new RestTemplate();
+ private final RestTemplate restTemplate;
- private String accessToken;
- private long tokenExpiryTime;
+ private volatile String accessToken;
+ private volatile long tokenExpiryTime;RestTemplate Bean을 설정 클래스에서 정의해 주세요:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(30))
.build();
}
}🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/service/AmadeusService.java
around lines 21 to 26, the class currently instantiates RestTemplate directly
and uses plain fields for accessToken and tokenExpiryTime which are unsafe for
concurrent access; change to have RestTemplate injected via constructor (define
a RestTemplate @Bean in a config class as suggested) and make token state
thread-safe by replacing accessToken and tokenExpiryTime with thread-safe
holders (e.g., AtomicReference<String> for token and AtomicLong for expiry) or
guard access/refresh with a synchronized block or ReentrantLock so only one
thread refreshes the token while others read stable values; ensure all
reads/writes use the chosen concurrency primitives and update code that
refreshes or reads the token accordingly.
| // 울산 공항 → 울산 이미지 (대왕암공원) | ||
| String ulsanImage = "https://images.unsplash.com/photo-1716902923395-1d9539c2266f?q=80&w=1674&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; | ||
| AIRPORT_IMAGES.put("USN", ulsanImage); // 울산공항 | ||
|
|
||
| // 청주 공항 → 청주 이미지 (속리산) | ||
| String cheongjuImage = "https://images.unsplash.com/photo-1716902923395-1d9539c2266f?q=80&w=1674&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; | ||
| AIRPORT_IMAGES.put("CJJ", cheongjuImage); // 청주국제공항 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미지 URL 중복을 수정하세요.
울산공항(USN)과 청주공항(CJJ)에 동일한 이미지 URL이 사용되고 있습니다. 각 지역을 대표하는 서로 다른 이미지를 사용해야 합니다.
Line 47의 청주 이미지를 다른 URL로 변경하세요:
// 청주 공항 → 청주 이미지 (속리산)
- String cheongjuImage = "https://images.unsplash.com/photo-1716902923395-1d9539c2266f?q=80&w=1674&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
+ String cheongjuImage = "https://images.unsplash.com/photo-[청주-대표-이미지-URL]";
AIRPORT_IMAGES.put("CJJ", cheongjuImage); // 청주국제공항Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/util/CountryImageMapper.java
around lines 42 to 48, the image URL for 청주공항 (CJJ) is a duplicate of 울산공항
(USN); replace the String assigned to cheongjuImage (line 47) with a different,
region-appropriate image URL (e.g., an Unsplash link representing 속리산/청주) and
keep the AIRPORT_IMAGES.put("CJJ", cheongjuImage) call unchanged so CJJ maps to
the new distinct URL.
| @Bean | ||
| public CorsConfigurationSource corsConfigurationSource() { | ||
| CorsConfiguration configuration = new CorsConfiguration(); | ||
| configuration.setAllowedOriginPatterns(Arrays.asList("*")); | ||
| configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); | ||
| configuration.setAllowedHeaders(Arrays.asList("*")); | ||
| configuration.setAllowCredentials(true); | ||
| configuration.setMaxAge(3600L); | ||
|
|
||
| UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | ||
| source.registerCorsConfiguration("/**", configuration); | ||
| return source; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CORS 설정의 보안 취약점을 수정하세요.
모든 origin 패턴을 허용(setAllowedOriginPatterns("*"))하면서 동시에 credentials를 활성화(setAllowCredentials(true))하는 것은 보안 위험이 있습니다. 이 조합은 임의의 도메인이 인증 정보를 포함한 요청을 보낼 수 있게 합니다.
다음 중 하나의 방법으로 수정하세요:
- 권장: 프로덕션에서 허용할 실제 origin 목록을 명시적으로 지정
- 또는 credentials를
false로 설정
다음 diff를 적용하여 프로덕션 준비를 위한 구성으로 변경하세요:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
- configuration.setAllowedOriginPatterns(Arrays.asList("*"));
+ configuration.setAllowedOrigins(Arrays.asList(
+ "http://localhost:3000", // 로컬 개발
+ "https://your-domain.com" // 프로덕션 도메인
+ ));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/global/config/SecurityConfig.java around
lines 60 to 72, the CORS config currently allows all origins with credentials
enabled which is insecure; change it to use an explicit list of allowed origins
for production (e.g., load from application properties or an environment
variable and call configuration.setAllowedOrigins(Arrays.asList(...))) and keep
setAllowCredentials(true) only when explicit origins are used; alternatively, if
you cannot supply explicit origins now, set
configuration.setAllowCredentials(false) to remove credential support; ensure
the UrlBasedCorsConfigurationSource registration remains the same.
#️⃣ 연관된 이슈
📝 작업 내용
1. 자동 데이터 업데이트 (스케줄러)
2. Amadeus API 연동
3. 자동 환율 변환
4. 한국어 공항명 매핑
5. 지역별 대표 이미지 매핑
📌 공유 사항
✅ 체크리스트
스크린샷 (선택)
💬 리뷰 요구사항 (선택)
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.