본문 바로가기
Spring/끄적끄적

WebClient 네이버 지도 비동기 API 호출하기 (1)

by wch_t 2024. 5. 20.

1. 목표

당일치기 API를 만들기

 

Request: 여행할 관광지 노드들 (첫 번째 노드가 출발 위치)

Response: 최적화 된 여행 경로를 계산하여 스케줄 정보를 DB에 저장하고, 해당 스케줄의 uuid를 반환

 

 


 

2. 개발 

 

Service 로직의 정리와 구현하면서 생긴 이슈를 다루는데 포커스를 맞추고 있어,

DTO 의 각 필드와 Repository 의 설명은 제외하였다.

 

1) Controller 정의

우선 유저가 앱 서비스 내에서 가고자 하는 관광지를 선택해, 당일치기 일정 생성을 할 것이다.

 

그럼 클라이언트에서 유저가 선택한 관광지 정보들을 Request 요청으로 보내고,

서버는 이를 당일치기 여행 스케줄을 작성해 반환한다.

 

 

[DayScheduleController]

@Controller
@RequiredArgsConstructor
@RequestMapping("/api/day")
public class DayScheduleController {
    private final DayTripService dayTripService;

    @PostMapping("")
    public ApiResponse<ScheduleResponseDto> addSchedule(@RequestBody @Valid NaverRequestDto naverRequestDto) {
        return ApiResponse.ok(dayTripService.setSchedule(naverRequestDto));
    }
}

 

 

 

2) Service 정의

Service 로직을 개발하기 전에, 미리 정리를 개발 플로우를 다음과 같이 정의했다.

 

NaverRequestDto는 당일치기 관광지 정보이다.
1. 출발지 노드에서 visited[False] 인 것 중에서 가장 가까운 노드를 찾는다. → NearNodeService()
2. 모든 노드를 방문할 때까지 계속한다.
3. ScheduleTour(관광지)로 Schedule(당일치기)을 생성한다.
4. Schedule - User 연관관계에 맞추어 파라미터에 set() 데이터를 채운다.
5. 벌크 쿼리로 Schedule, ScheduleTour DB에 반영한다.

 


 

 

1~2. ScheduleTours 정의

 

- 현재 위치에서 가장 가까운 위치로 이동하는 순서로 정의한다. (그리디 알고리즘)

Map<Long, Boolean> visited = new HashMap<>(); // 관광지 방문 여부 파악
List<Node> nodes = naverRequestDto.getKotrip().get(0).getNodes(); // 관광지 리스트

// 관광지 리스트 미방문 초기화
for (Node node : nodes) {
    visited.put(node.getId(), false);
}

ArrayList<ScheduleTour> scheduleTours = new ArrayList<>();
Node start = nodes.get(0);
for (int i = 0; i < nodes.size(); ++i) {
    visited.put(start.getId(), true); // 방문 처리
    scheduleTours.add(new ScheduleTour(start.getId(), start.getName(), 0L, start.getImageUrl(), start.getLongitude(), start.getLatitude(), null)); // 노드 정보로 ScheduleTour를 만든다.

    // 현재 노드에서 가까운 노드 찾기
    Node nearNode = nearNodeService.getNearNode(start, nodes, visited);
    start = nearNode;
}

 

 

 

3~4. Schedule 객체 정의

 

- User의 히스토리에 저장할 수 있게끔, 위의 scheduleTours(여행경로)를 포함하여 Schedule 객체를 정의한다.
- Schedule∙User 연관관계에 맞추어 User에 Schedule을 넣는다.

Q. 이 때, 당일치기이므로 List<Schedule>로 만들어야 할 필요가 있을까?
A. n박 m일 DTO 구조와 동일하게 사용하기 위해 List<>로 정의하였다.

// 당일치기이므로 Schedule이 1개밖에 없으나, 기존 양식과 맞추기 위해 ArrayList로 정의했다.
ArrayList<Schedule> schedules = new ArrayList<>();

// 유저 조회
Authentication authentication = getAuthentication();
User user = userRepository.findUserByNickname(authentication.getName()).orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없습니다."));

// 랜덤값
String uuid = ClassificationId.getID();
String uuid2 = ClassificationId.getID();
Schedule schedule = Schedule.toEntity(naverRequestDto.getTitle(), uuid, naverRequestDto.getAreaId(),
        naverRequestDto.getKotrip().get(0).getDate(), user, scheduleTours, uuid2);
schedules.add(schedule);

 

 

 

5. Bulk 쿼리로 Schedule, ScheduleTour를 DB에 반영한다.

 

- '전체 여행 일정', '최적화된 여행 경로'을 DB에 넣어준다.

scheduleJdbcRepository.saveAll(schedules);
scheduleTourJdbcRepository.saveAll(scheduleTours);

 


 

 

[DayTripService]

@Service
@RequiredArgsConstructor
public class DayTripService {
    private final NearNodeService nearNodeService;
    private final UserRepository userRepository;
    private final ScheduleJdbcRepository scheduleJdbcRepository;
    private final ScheduleTourJdbcRepository scheduleTourJdbcRepository;

    public ScheduleResponseDto setSchedule(NaverRequestDto naverRequestDto) {
    
        /**
         * NaverRequestDto는 당일치기 관광지 정보이다.
         *
         * 1. 출발지 노드에서 visited[False] 인 것 중에서 가장 가까운 노드를 찾는다. NearNodeService()
         * 2. 모든 노드를 방문할 때까지 계속한다.
         * 3. ScheduleTour(관광지)로 Schedule(당일치기)을 생성한다.
         * 4. Schedule - User 연관관계에 맞추어 파라미터에 set() 데이터를 채운다.
         * 5. 벌크 쿼리로 Schedule, ScheduleTour DB에 반영한다.
         */


        /** 1 ~ 2.
         * ScheduleTours 정의
         * - 현재 위치에서 가장 가까운 위치로 이동하는 순서로 정의한다. (그리디 알고리즘)
         */
        Map<Long, Boolean> visited = new HashMap<>();
        List<Node> nodes = naverRequestDto.getKotrip().get(0).getNodes(); // 각 관광지들

        // 관광지 리스트 미방문 초기화
        for (Node node : nodes) {
            visited.put(node.getId(), false);
        }

        ArrayList<ScheduleTour> scheduleTours = new ArrayList<>();
        Node start = nodes.get(0);
        for (int i = 0; i < nodes.size(); ++i) {
            visited.put(start.getId(), true); // 방문 처리
            scheduleTours.add(new ScheduleTour(start.getId(), start.getName(), 0L, start.getImageUrl(), start.getLongitude(), start.getLatitude(), null)); // 노드 정보로 ScheduleTour를 만든다.

            /**
             * 현재 노드에서 가까운 노드 찾기
             */
            Node nearNode = nearNodeService.getNearNode(start, nodes, visited);
            start = nearNode;
        }



        /** 3 ~ 4.
         * Schedule 객체 정의
         * - User의 히스토리에 저장할 수 있게끔, 위의 scheduleTours(여행경로)를 포함하여 Schedule 객체를 정의한다.
         * - Schedule - User 연관관계에 맞추어 User에 Schedule을 넣는다.
         *
         * Q. 이 때, 당일치기이므로 List<Schedule>로 만들어야 할 필요가 있을까?
         * A. n박 m일 DTO 구조와 동일하게 사용하기 위해 List<>로 정의하였다.
         */
        // 당일치기이므로 Schedule이 1개밖에 없으나, 기존 양식과 맞추기 위해 ArrayList로 정의했다.
        ArrayList<Schedule> schedules = new ArrayList<>();

        // 유저 조회
        Authentication authentication = getAuthentication();
        User user = userRepository.findUserByNickname(authentication.getName()).orElseThrow(() -> new UsernameNotFoundException("유저를 찾을 수 없습니다."));

        // 랜덤값
        String uuid = ClassificationId.getID();
        String uuid2 = ClassificationId.getID();
        Schedule schedule = Schedule.toEntity(naverRequestDto.getTitle(), uuid, naverRequestDto.getAreaId(),
                naverRequestDto.getKotrip().get(0).getDate(), user, scheduleTours, uuid2);
        schedules.add(schedule);



        /** 5.
         * 벌크 쿼리로 Schedule, ScheduleTour를 DB에 반영한다.
         */
        scheduleJdbcRepository.saveAll(schedules);
        scheduleTourJdbcRepository.saveAll(scheduleTours);

        return new ScheduleResponseDto(uuid);
    }


    private Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}