flowchart TD
A[큐 조회 요청] --> B[모든 PCC 조회]
B --> C{LEGACY_PCC?}
C -->|Yes| D[제외]
C -->|No| E[PCC별 세션 생성]
E --> F[병렬 큐 처리]
F --> G[Queue 5 조회]
F --> H[Queue 6 조회]
F --> I[Queue 7 조회]
F --> J[Queue 20 조회]
G --> K[큐 카운트 조회]
H --> K
I --> K
J --> K
K --> L{카운트 > 0?}
L -->|No| M[emptyList]
L -->|Yes| N[큐 PNR 목록 조회]
N --> O[QueuePnrInfo 생성]
O --> P[전체 결과 병합]
style C fill:#E8B4B8,stroke:#333,stroke-width:2px,color:#000
style L fill:#F39C9C,stroke:#333,stroke-width:2px,color:#000
// SabreQueueService.kt:27-30companion object { val TARGET_ORIGIN_QUEUE_NUMBERS = listOf("5", "6", "7", "20") val LEGACY_PCC = listOf("3OGJ", "7CZJ")}
Queue 5: 일반 처리 큐 1
Queue 6: 일반 처리 큐 2
Queue 7: 특별 처리 큐
Queue 20: 긴급 처리 큐
Legacy PCC: 이전 시스템 PCC (제외 대상)
2.3 큐 제거 프로세스 (remove)
2.3.1 순차 제거 로직
// SabreQueueService.kt:85-138@Retryable(maxAttempts = 3, backoff = Backoff(delay = 5000))fun remove( queueNumber: String, pcc: String, pnrs: Set<String>,): Boolean { // 1. PCC별 세션 생성 val (channel, sabreProperties) = sabreProperties.getApiProperties(pcc = pcc) val token = sabreClient.getSessionToken( channel = channel, funnel = sabreProperties.funnel, targetDate = sabreProperties.period?.from ?: now() ) val removePnrs = mutableListOf<String>() return try { // 2. 큐 카운트 조회 val queueCount = sabreClient.getPnrCountInQueue( token = token, queueNumber = queueNumber, pcc = pcc, ) // 3. 큐 작업 모드 최초 진입 - 첫 번째 PNR 조회 var currentPnr = sabreClient.getPnrsInQueueAction( token = token, pcc = pcc, queueNumber = queueNumber, ).firstOrNull() // 4. 큐 내 모든 PNR 순회 repeat(queueCount) { // 5. 제거 대상 여부 확인 후 액션 실행 val nextPnr = sabreClient.getPnrsInQueueAction( token = token, pcc = pcc, actionType = if (currentPnr in pnrs) QueueAccessActionType.QR else QueueAccessActionType.I // QR: 큐에서 제거, I: 건너뛰기 ).firstOrNull() if (currentPnr in pnrs) removePnrs.add(currentPnr!!) currentPnr = nextPnr } logger.info("[${Supplier.SABRE}] Remove - Queue Number: $queueNumber, Counts: ${pnrs.count()}, PNRs:$removePnrs, PCC: $pcc") true } catch (e: Exception) { logger.error("[${Supplier.SABRE}] Remove Failed - Queue Number: $queueNumber, Target PNRs: $pnrs, Remove PNRs: $removePnrs, PCC: $pcc") slackService.sendQueueFail(supplier = "${Supplier.SABRE.name}($pcc)", reason = e.message) false } finally { // 6. 세션 종료 sabreClient.closeSessionToken( token = token, channel = channel, funnel = sabreProperties.funnel, targetDate = sabreProperties.period?.from ?: now() ) }}
2.3.2 제거 프로세스 플로우
flowchart TD
A[제거 요청] --> B[세션 생성]
B --> C[큐 카운트 조회]
C --> D[첫 PNR 조회]
D --> E{repeat queueCount}
E --> F[현재 PNR 확인]
F --> G{제거 대상?}
G -->|Yes| H[QR 액션<br/>큐에서 제거]
G -->|No| I[I 액션<br/>건너뛰기]
H --> J[다음 PNR 조회]
I --> J
J --> K[removePnrs 기록]
K --> L{다음 PNR?}
L -->|Yes| F
L -->|No| M[완료]
M --> N[로깅 및 true 반환]
style G fill:#E8B4B8,stroke:#333,stroke-width:2px,color:#000
style L fill:#F39C9C,stroke:#333,stroke-width:2px,color:#000
3. QueueAccessActionType
3.1 액션 타입 정의
// QueueAccessActionType.kt:3-15enum class QueueAccessActionType { QR, // 조회 중인 PNR을 Q에서 제거하고 다음 PNR 조회 I, // PNR 변경 작업 취소 후 Q에 남겨두고 다음 PNR 조회 E, // PNR 변경 작업 저장 후 Q에서 제거하고 다음 PNR 조회 EL, // PNR 변경 작업 저장 후 Q에 남겨두고 다음 PNR 조회 EW, // PNR 코드 업데이트 후 Q에서 제거하고 다음 PNR 조회 EWL, // PNR 코드 업데이트 후 Q에 남겨두고 다음 PNR 조회 EWR, // PNR 코드 업데이트 후 해당 PNR 재조회 QXI, // 작업 종료 및 가장 마지막 PNR 변경 작업 취소 QXE, // 작업 종료 및 가장 마지막 PNR 변경 작업 저장 QXIR,// 작업 종료 및 가장 마지막 PNR 변경 작업 취소 후 재조회 QXER,// 작업 종료 및 가장 마지막 PNR 변경 작업 저장 후 재조회}
3.2 주요 액션 사용
flowchart LR
A[PNR 확인] --> B{처리 대상?}
B -->|Yes| C[QR 액션]
B -->|No| D[I 액션]
C --> E[큐에서 제거]
D --> F[큐에 유지]
E --> G[다음 PNR]
F --> G
style B fill:#E8B4B8,stroke:#333,stroke-width:2px,color:#000
style C fill:#F39C9C,stroke:#333,stroke-width:2px,color:#000
style D fill:#A8D5BA,stroke:#333,stroke-width:2px,color:#000
// SabreClient.kt:810-843fun getPnrsInQueueAction( token: String, pcc: String, queueNumber: String? = null, actionType: QueueAccessActionType? = null, listOfRecordLocator: Boolean = false,): List<String> { val (_, sabreApiProperties) = sabreProperties.getApiProperties(pcc = pcc) // 1. 초기 진입 또는 액션 실행 분기 val request = if (actionType == null) { // 초기 진입: 큐 접근 및 PNR 목록 조회 QueueAccessRQ.of( pseudoCityCode = pcc, queueNumber = queueNumber!!, listOfRecordLocator = listOfRecordLocator, ) } else { // 액션 실행: 현재 PNR 처리 후 다음 PNR 조회 QueueAccessRQ.ofAction( actionType = actionType, ) }.withToken(token) return sabreApiProperties.endpoint .post(request) .header(headerMap) .requestBodyConvert(soapRequestBodyConverter(sabreApiProperties)) .execute<SabreResponse<QueueAccessRS>>(soapBodyDeserializerOf(logger, objectMapper)) .fold( success = { response -> response.body!!.checkError() response.body.lines?.mapNotNull { it.uniqueID?.id } ?: emptyList() }, failure = { throw it.handleSoapFaultException(ErrorMessage.QUEUE_ACCESS_FAILED) } )}
4.3 QueueAccessRQ 요청 구조
// QueueAccessRQ.kt:30-51companion object { // 초기 큐 접근 요청 fun of( pseudoCityCode: String, queueNumber: String, listOfRecordLocator: Boolean = false, ): QueueAccessRQ { return QueueAccessRQ( queueIdentifiers = listOf( QueueIdentifier( list = Item(listOfRecordLocator = listOfRecordLocator), pseudoCityCode = pseudoCityCode, number = queueNumber ) ) ) } // 액션 실행 요청 fun ofAction(actionType: QueueAccessActionType): QueueAccessRQ { return QueueAccessRQ( navigation = Navigation(action = actionType.name) ) }}
5. 병렬 처리 전략
5.1 큐 병렬 조회
// SabreQueueService.kt:45-69withBlocking(Dispatchers.IO) { TARGET_ORIGIN_QUEUE_NUMBERS.pmap { originQueueNumber -> // 병렬 처리 val queueCount = sabreClient.getPnrCountInQueue(...) if (queueCount > 0) { sabreClient.getPnrsInQueueAction(...) .map { pnr -> QueuePnrInfo(pnr = pnr, queueNumber = originQueueNumber, pccOid = pcc) } } else { emptyList() } }.getOrEmpty()}
5.2 병렬 처리 플로우
graph TD
A[PCC별 조회 시작] --> B[pmap 병렬 처리]
B --> C[Queue 5 스레드]
B --> D[Queue 6 스레드]
B --> E[Queue 7 스레드]
B --> F[Queue 20 스레드]
C --> G[Queue 5 카운트]
D --> H[Queue 6 카운트]
E --> I[Queue 7 카운트]
F --> J[Queue 20 카운트]
G --> K[Queue 5 PNR 목록]
H --> L[Queue 6 PNR 목록]
I --> M[Queue 7 PNR 목록]
J --> N[Queue 20 PNR 목록]
K --> O[결과 병합]
L --> O
M --> O
N --> O
O --> P[QueuePnrInfo 리스트]
style B fill:#E8B4B8,stroke:#333,stroke-width:2px,color:#000
style O fill:#F39C9C,stroke:#333,stroke-width:2px,color:#000
// 가능하다면 QueueMoveRQ API를 사용한 일괄 제거fun removeBulk(queueNumber: String, pcc: String, pnrs: Set<String>): Boolean { // QueueMove API로 다른 큐로 이동 후 해당 큐 전체 삭제 // 또는 Sabre API 문서에서 일괄 제거 방법 조사}