diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/controller/AirplaneController.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/controller/AirplaneController.java new file mode 100644 index 0000000..34a86f8 --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/controller/AirplaneController.java @@ -0,0 +1,39 @@ +package com.example.EnjoyTripBackend.controller; + +import com.example.EnjoyTripBackend.dto.NonPagingResponseResult; +import com.example.EnjoyTripBackend.dto.ResponseResult; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneRequestDto; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneResponseDto; +import com.example.EnjoyTripBackend.service.AirplaneService; +import com.example.EnjoyTripBackend.util.LimitedSizePagination; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class AirplaneController { + + private final AirplaneService airplaneService; + + @GetMapping("/airplanes") + public ResponseEntity>> airplaneList(@PageableDefault(size = 20) Pageable pageable){ + return ResponseEntity.ok().body(airplaneService.airplaneList(pageable)); + } + + @GetMapping("/airplanes/{id}") + public ResponseEntity> airplaneDetail(@PathVariable("id")Long id){ + return ResponseEntity.ok().body(airplaneService.findById(id)); + } + + @PostMapping("/airplanes/search") + @LimitedSizePagination(maxSize = 20) + public ResponseEntity>> airplaneSearchList(@PageableDefault(size = 20) Pageable pageable, @RequestBody AirplaneRequestDto requestDto){ + return ResponseEntity.ok().body(airplaneService.airplaneSearchList(pageable, requestDto)); + } +} \ No newline at end of file diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/domain/Airplane.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/domain/Airplane.java new file mode 100644 index 0000000..e65ccbe --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/domain/Airplane.java @@ -0,0 +1,20 @@ +package com.example.EnjoyTripBackend.domain; + +import lombok.*; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Airplane { + private Long id; + private String airlineNm; + private String arrAirportNm; + private String depAirportNm; + private String arrPlandTime; + private String depPlandTime; + private String economyCharge; + private String prestigeCharge; + private String vihicleId; + +} diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneRequestDto.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneRequestDto.java new file mode 100644 index 0000000..fb1bbba --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneRequestDto.java @@ -0,0 +1,15 @@ +package com.example.EnjoyTripBackend.dto.airplane; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class AirplaneRequestDto { + private String depAirportNm; + private String arrAirportNm; + private String depPlandTime; + private String airlineNm; +} diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneResponseDto.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneResponseDto.java new file mode 100644 index 0000000..3c5f42a --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/dto/airplane/AirplaneResponseDto.java @@ -0,0 +1,20 @@ +package com.example.EnjoyTripBackend.dto.airplane; + +import lombok.*; + +@Setter +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AirplaneResponseDto { + private Long id; + private String airlineNm; + private String arrAirportNm; + private String depAirportNm; + private String arrPlandTime; + private String depPlandTime; + private String economyCharge; + private String prestigeCharge; + private String vihicleId; +} diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/exception/ErrorCode.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/exception/ErrorCode.java index 70880ac..833780b 100644 --- a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/exception/ErrorCode.java +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/exception/ErrorCode.java @@ -21,7 +21,8 @@ public enum ErrorCode { FAIL_INSERT_GOLF_DATA(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스에 골프 데이터 저장에 실패하였습니다"), FAIL_COMMUNICATE_TOSSPAYMENT_API(HttpStatus.INTERNAL_SERVER_ERROR,"토스페이먼트 API와의 통신에 실패하였습니다."), FAIL_COMMUNICATE_EXTERNAL_API(HttpStatus.INTERNAL_SERVER_ERROR,"외부 API와의 통신에 실패하였습니다."), - Limited_Size_Pagination(HttpStatus.INTERNAL_SERVER_ERROR,"최대 페이징 사이지를 초과하였습니다."); + Limited_Size_Pagination(HttpStatus.INTERNAL_SERVER_ERROR,"최대 페이징 사이지를 초과하였습니다."), + FAIL_INSERT_AIRPLANE_DATA(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스에 항공권 데이터 저장에 실패하였습니다"); private final HttpStatus httpstatus; private final String message; diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/repository/AirplaneRepository.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/repository/AirplaneRepository.java new file mode 100644 index 0000000..fc41452 --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/repository/AirplaneRepository.java @@ -0,0 +1,18 @@ +package com.example.EnjoyTripBackend.repository; + +import com.example.EnjoyTripBackend.domain.Airplane; +import com.example.EnjoyTripBackend.dto.PageRequestList; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneResponseDto; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Optional; + +@Mapper +public interface AirplaneRepository { + Long save(Airplane airplane); + List findAll(PageRequestList requestList); + Optional findById(Long id); + List findAllBySearch(PageRequestList requestList); + Long findTotalCount(); +} \ No newline at end of file diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/service/AirplaneService.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/service/AirplaneService.java new file mode 100644 index 0000000..3b034f2 --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/service/AirplaneService.java @@ -0,0 +1,72 @@ +package com.example.EnjoyTripBackend.service; + +import com.example.EnjoyTripBackend.domain.Airplane; +import com.example.EnjoyTripBackend.dto.NonPagingResponseResult; +import com.example.EnjoyTripBackend.dto.PageRequestList; +import com.example.EnjoyTripBackend.dto.ResponseResult; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneRequestDto; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneResponseDto; +import com.example.EnjoyTripBackend.dto.golf.GolfRequestDto; +import com.example.EnjoyTripBackend.dto.golf.GolfResponseDto; +import com.example.EnjoyTripBackend.dto.place.PlaceResponseDto; +import com.example.EnjoyTripBackend.exception.EnjoyTripException; +import com.example.EnjoyTripBackend.exception.ErrorCode; +import com.example.EnjoyTripBackend.repository.AirplaneRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Date; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class AirplaneService { + + private final AirplaneRepository airplaneRepository; + + @Transactional + public void save(AirplaneResponseDto airplaneResponseDto) { + Airplane airplane = Airplane.builder() + .airlineNm(airplaneResponseDto.getAirlineNm()) + .arrAirportNm(airplaneResponseDto.getArrAirportNm()) + .depAirportNm(airplaneResponseDto.getDepAirportNm()) + .arrPlandTime(airplaneResponseDto.getArrPlandTime()) + .depPlandTime(airplaneResponseDto.getDepPlandTime()) + .economyCharge(airplaneResponseDto.getEconomyCharge()) + .prestigeCharge(airplaneResponseDto.getPrestigeCharge()) + .vihicleId(airplaneResponseDto.getVihicleId()) + .build(); + + airplaneRepository.save(airplane); + } + + public ResponseResult> airplaneList(Pageable pageable) { + PageRequestList requestList = PageRequestList.builder() + .pageable(pageable) + .build(); + + long totalCount = airplaneRepository.findTotalCount(); + int totalPages = (int) Math.ceil((double) totalCount / pageable.getPageSize()); + + return ResponseResult.of("항공권 정보 게시글 목록입니다.", airplaneRepository.findAll(requestList),totalPages); + } + + public NonPagingResponseResult findById(Long id) { + return NonPagingResponseResult.of("항공권 상세 정보 게시글 입니다.", airplaneRepository.findById(id).orElseThrow(() -> new EnjoyTripException(ErrorCode.CONTENT_NOT_FOUNT))); + } + + public ResponseResult> airplaneSearchList(Pageable pageable, AirplaneRequestDto airplaneRequestDto) { + PageRequestList requestList = PageRequestList.builder() + .pageable(pageable) + .data(airplaneRequestDto) + .build(); + List airplanes = airplaneRepository.findAllBySearch(requestList); + long totalCount = airplanes.size(); + int totalPages = (int) Math.ceil((double) totalCount / pageable.getPageSize()); + return ResponseResult.of("검색어 기반 항공권 정보 게시글 목록입니다.", airplanes, totalPages); + } +} diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/AirplaneInfoCollector.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/AirplaneInfoCollector.java new file mode 100644 index 0000000..c403e05 --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/AirplaneInfoCollector.java @@ -0,0 +1,108 @@ +package com.example.EnjoyTripBackend.util; + +import com.example.EnjoyTripBackend.domain.Airplane; +import com.example.EnjoyTripBackend.dto.airplane.AirplaneResponseDto; +import com.example.EnjoyTripBackend.exception.EnjoyTripException; +import com.example.EnjoyTripBackend.exception.ErrorCode; +import com.example.EnjoyTripBackend.service.AirplaneService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class AirplaneInfoCollector { + + @Value("${flight.service.key}") + private String serviceKey; + + private final AirplaneService airplaneService; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + @Scheduled(cron = "0 48 19 * * *") // 매일 오후 7시 48분에 실행 + public void fetchFlightInfo() { + LocalDate today = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + + List depAirportIds = List.of("NAARKJB", "NAARKJJ", "NAARKJK", "NAARKJY", "NAARKNW", "NAARKNY", "NAARKPC", "NAARKPK", "NAARKPS", "NAARKPU", "NAARKSI", "NAARKSS", "NAARKTH", "NAARKTN", "NAARKTU"); + List arrAirportIds = List.of("NAARKJB", "NAARKJJ", "NAARKJK", "NAARKJY", "NAARKNW", "NAARKNY", "NAARKPC", "NAARKPK", "NAARKPS", "NAARKPU", "NAARKSI", "NAARKSS", "NAARKTH", "NAARKTN", "NAARKTU"); + List airlineIds = List.of("AAR", "ABL", "ASV", "ESR", "FGW", "HGG", "JJA", "JNA", "KAL", "TWB"); + + for (int i = 0; i < 31; i++) { // Collect data for 31 days + LocalDate date = today.plusDays(i); + String depPlandTime = date.format(formatter); + + for (String depAirportId : depAirportIds) { + for (String arrAirportId : arrAirportIds) { + if (!depAirportId.equals(arrAirportId)) { // Ensure departure and arrival airports are different + for (String airlineId : airlineIds) { + fetchAndSaveFlightsForDate(depAirportId, arrAirportId, depPlandTime, airlineId); + } + } + } + } + } + } + + private void fetchAndSaveFlightsForDate(String depAirportId, String arrAirportId, String depPlandTime, String airlineId) { + String url = String.format("http://apis.data.go.kr/1613000/DmstcFlightNvgInfoService/getFlightOpratInfoList?serviceKey=%s&_type=json&depAirportId=%s&arrAirportId=%s&depPlandTime=%s&airlineId=%s", + serviceKey, depAirportId, arrAirportId, depPlandTime, airlineId); + + ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class); + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + String responseBody = responseEntity.getBody(); + + try { + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode itemsNode = rootNode.path("response").path("body").path("items").path("item"); + + if (itemsNode.isArray()) { + for (JsonNode itemNode : itemsNode) { + String airlineNm = getSafeText(itemNode, "airlineNm"); + String arrAirportNm = getSafeText(itemNode, "arrAirportNm"); + String depAirportNm = getSafeText(itemNode, "depAirportNm"); + String depPlandTimeStr = getSafeText(itemNode, "depPlandTime"); + String arrPlandTimeStr = getSafeText(itemNode, "arrPlandTime"); + String economyCharge = getSafeText(itemNode, "economyCharge"); + String prestigeCharge = getSafeText(itemNode, "prestigeCharge"); + String vihicleId = getSafeText(itemNode, "vihicleId"); + + if (arrAirportNm != null && depAirportNm != null && !arrAirportNm.equals(depAirportNm)) { + AirplaneResponseDto airplaneDto = new AirplaneResponseDto(); + airplaneDto.setAirlineNm(airlineNm); + airplaneDto.setArrAirportNm(arrAirportNm); + airplaneDto.setDepAirportNm(depAirportNm); + airplaneDto.setDepPlandTime(depPlandTimeStr); + airplaneDto.setArrPlandTime(arrPlandTimeStr); + airplaneDto.setEconomyCharge(economyCharge); + airplaneDto.setPrestigeCharge(prestigeCharge); + airplaneDto.setVihicleId(vihicleId); + + airplaneService.save(airplaneDto); + } + } + } + } catch (Exception e) { + throw new EnjoyTripException(ErrorCode.FAIL_INSERT_AIRPLANE_DATA, e.getMessage()); + } + } else { + throw new EnjoyTripException(ErrorCode.FAIL_INSERT_AIRPLANE_DATA, "Failed to fetch airplane data from the API"); + } + } + + private String getSafeText(JsonNode node, String key) { + JsonNode valueNode = node.get(key); + return valueNode != null ? valueNode.asText() : null; + } +} diff --git a/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/DataUpdater.java b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/DataUpdater.java new file mode 100644 index 0000000..e526b88 --- /dev/null +++ b/EnjoyTripBackend/src/main/java/com/example/EnjoyTripBackend/util/DataUpdater.java @@ -0,0 +1,22 @@ +package com.example.EnjoyTripBackend.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class DataUpdater implements CommandLineRunner { + + private final AirplaneInfoCollector airplaneInfoCollector; + + @Autowired + public DataUpdater(AirplaneInfoCollector airplaneInfoCollector) { + this.airplaneInfoCollector = airplaneInfoCollector; + } + + @Override + public void run(String... args) throws Exception { + // 애플리케이션이 시작될 때 실행되는 코드 +// airplaneInfoCollector.fetchFlightInfo(); + } +} diff --git a/EnjoyTripBackend/src/main/resources/config/mybatis-config.xml b/EnjoyTripBackend/src/main/resources/config/mybatis-config.xml index f4b1f03..4c5ae3a 100644 --- a/EnjoyTripBackend/src/main/resources/config/mybatis-config.xml +++ b/EnjoyTripBackend/src/main/resources/config/mybatis-config.xml @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/EnjoyTripBackend/src/main/resources/mapper/airplane-query.xml b/EnjoyTripBackend/src/main/resources/mapper/airplane-query.xml new file mode 100644 index 0000000..51a3132 --- /dev/null +++ b/EnjoyTripBackend/src/main/resources/mapper/airplane-query.xml @@ -0,0 +1,43 @@ + + + + + + + INSERT + INTO airplane (airlineNm, arrAirportNm, depAirportNm, arrPlandTime, depPlandTime, economyCharge, prestigeCharge, vihicleId) + VALUES (#{airlineNm}, #{arrAirportNm}, #{depAirportNm}, #{arrPlandTime}, #{depPlandTime}, #{economyCharge}, #{prestigeCharge}, #{vihicleId}) + + + + + + + + + + +