diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java index f282010..9da4e3e 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java @@ -18,12 +18,19 @@ import org.springframework.web.socket.client.WebSocketClient; import org.springframework.lang.NonNull; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -41,6 +48,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler { private static final DateTimeFormatter BIZ_KEY_DATE_8 = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final long DEFAULT_DFIE_TTL_SECONDS = TimeUnit.DAYS.toSeconds(2); private static final long MIN_DFIE_TTL_SECONDS = 60L; + private static final Map ICAO_TO_IATA_PREFIX = loadIcaoToIataPrefixMap(); private final WebSocketClient webSocketClient; private final FlightSdkProperties properties; @@ -474,6 +482,62 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler { return s.toUpperCase(); } + /** + * TISFLIGHT 的 FlNo/FlightId 可能使用三字航司码(ICAO), + * 这里按内置对照表转换为二字码(IATA)后再参与 Redis key 拼装。 + */ + private static String normalizeTisFlightNo(String raw) { + String normalized = normalizeFlightNo(raw); + if (normalized == null || normalized.length() < 4) { + return normalized; + } + String prefix = normalized.substring(0, 3); + String mappedPrefix = ICAO_TO_IATA_PREFIX.get(prefix); + if (mappedPrefix == null || mappedPrefix.isBlank()) { + return normalized; + } + return mappedPrefix + normalized.substring(3); + } + + private static Map loadIcaoToIataPrefixMap() { + Map map = new HashMap<>(); + try (InputStream in = AdxpFlightServiceWebSocketClient.class.getResourceAsStream("/airline-code-map.csv")) { + if (in == null) { + log.warn("未找到航司二三字码映射资源: /airline-code-map.csv"); + return Collections.emptyMap(); + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String line; + boolean firstLine = true; + while ((line = reader.readLine()) != null) { + String row = line.trim(); + if (row.isEmpty()) { + continue; + } + if (firstLine) { + firstLine = false; + if (row.toUpperCase().startsWith("IATA,ICAO")) { + continue; + } + } + String[] parts = row.split(",", 2); + if (parts.length != 2) { + continue; + } + String iata = parts[0].trim().toUpperCase(); + String icao = parts[1].trim().toUpperCase(); + if (iata.matches("^[A-Z0-9]{2}$") && icao.matches("^[A-Z0-9]{3}$")) { + map.put(icao, iata); + } + } + } + } catch (Exception e) { + log.warn("加载航司二三字码映射失败,将跳过三字码转二字码: {}", e.getMessage()); + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(map); + } + /** * 将 BizKey 按最多 3 段安全切分,避免空值导致 NPE。 */ @@ -1009,8 +1073,8 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler { String fuId = getTextContentAny(root, "FuId", "FUID", "FuID"); String flightIdRaw = getTextContentAny(root, "FlightId", "FlightID", "FlId", "FLID"); String flightNumberRaw = getTextContent(root, "FlNo"); - String flightNumber = normalizeFlightNo(flightNumberRaw); - String flightId = normalizeFlightNo(flightIdRaw); + String flightNumber = normalizeTisFlightNo(flightNumberRaw); + String flightId = normalizeTisFlightNo(flightIdRaw); String contactCross = getTextContent(root, "ContactCross"); String type = getTextContent(root, "Type"); long nowMs = System.currentTimeMillis(); @@ -1036,11 +1100,13 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler { } } - String fuIdFlightNo = firstSegmentFromBizLike(fuId); - String bizFlightNo = firstSegmentFromBizLike(bizKey); + String fuIdFlightNo = normalizeTisFlightNo(firstSegmentFromBizLike(fuId)); + String bizFlightNo = normalizeTisFlightNo(firstSegmentFromBizLike(bizKey)); String resolvedFlightNo = flightNumber != null ? flightNumber : (flightId != null ? flightId : (fuIdFlightNo != null ? fuIdFlightNo : bizFlightNo)); + log.info("TISFLIGHT航班号转换: flNoRaw={}, flNoNorm={}, flightIdRaw={}, flightIdNorm={}, resolvedFlightNo={}", + flightNumberRaw, flightNumber, flightIdRaw, flightId, resolvedFlightNo); FlightNotificationDTO dto = new FlightNotificationDTO(); dto.setFlightNo(resolvedFlightNo != null ? resolvedFlightNo : flightNumberRaw); @@ -1049,32 +1115,19 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler { dto.setContactCross(contactCross); } - // TISFLIGHT 特殊:可能无 BizKey。无 BizKey 时先 FuId 关联,再按航班号回退。 - String normalizedBizKey = resolveTisFlightBizKey(bizKey, fuId, flightId, resolvedFlightNo); String flightKey = buildFlightRedisKey(resolvedFlightNo); - String bizRedisKey = buildBizRedisKey(normalizedBizKey); - boolean canUpdateRouteParams = normalizedBizKey != null; - if (flightKey != null || bizRedisKey != null) { + if (flightKey != null) { try { - setValueOnFlightAndBizKeys(flightKey, bizRedisKey, "flightNumber", resolvedFlightNo); - if (normalizedBizKey != null) { - setValueOnFlightAndBizKeys(flightKey, bizRedisKey, "bizKey", normalizedBizKey); - } - updateActiveBizKey(flightKey, normalizedBizKey, nowMs); + redisCache.setCacheMapValue(flightKey, "flightNumber", resolvedFlightNo); // contactCross 仅对进港有效,避免进出港参数串用 if ("IN".equalsIgnoreCase(routeType) && contactCross != null && !contactCross.isBlank()) { - setValueOnFlightAndBizKeys(flightKey, bizRedisKey, "contactCross", contactCross); - setValueOnFlightAndBizKeys(flightKey, bizRedisKey, "contactCrossTs", String.valueOf(nowMs)); + redisCache.setCacheMapValue(flightKey, "contactCross", contactCross); + redisCache.setCacheMapValue(flightKey, "contactCrossTs", String.valueOf(nowMs)); } - // TISFLIGHT 也可提供 seat,作为 CRAFTSEAT 的补充来源 - if (!canUpdateRouteParams && contactCross != null) { - log.warn("TISFLIGHT未建立BizKey关联,跳过contactCross/seat覆盖: flNo={}, flightId={}, fuId={}, bizKey={}, resolvedFlightNo={}", - flightNumber, flightId, fuId, bizKey, resolvedFlightNo); - } - log.info("TISFLIGHT航班号解析: flNo={}, flightId={}, fuId={}, bizKey={}, selectedBizKey={}, resolvedFlightNo={}, hasBizKey={}", - flightNumber, flightId, fuId, bizKey, normalizedBizKey, resolvedFlightNo, normalizedBizKey != null); + log.info("TISFLIGHT航班号解析(已应用三字转二字): flNoRaw={}, flightIdRaw={}, fuId={}, bizKey={}, resolvedFlightNo={}", + flightNumberRaw, flightIdRaw, fuId, bizKey, resolvedFlightNo); } catch (Exception e) { log.error("存储航班TISFLIGHT数据到Redis失败: {}", e.getMessage()); } diff --git a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java index 1341a9f..c9b62ae 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java @@ -1149,11 +1149,6 @@ public class DataProcessingService { normalizedBizKey = normalizeRedisString(redisCache.getCacheMapValue(flightKey, "activeBizKey")); normalizedBizKey = normalizeBizKey(normalizedBizKey); } - if (normalizedBizKey == null) { - log.info("路由Redis参数未齐全(缺少bizKey),等待补齐后重试: flightNo={}, routeType={}", - normalizedFlightNo, normalizedRouteType); - return false; - } String bizRedisKey = buildBizRedisKey(normalizedBizKey); // 从 Redis 获取参数快照 diff --git a/qaup-collision/src/main/resources/airline-code-map.csv b/qaup-collision/src/main/resources/airline-code-map.csv new file mode 100644 index 0000000..92cbdf2 --- /dev/null +++ b/qaup-collision/src/main/resources/airline-code-map.csv @@ -0,0 +1,198 @@ +IATA,ICAO +PO,CYW +PR,PAL +PY,PYN +RB,RBW +C3,TDR +SC,CDG +TG,THA +TH,TSE +TI,TIS +TW,TWB +UI,UVS +US,USA +VP,CDF +WU,CWU +8L,LKE +YD,YDH +YZ,YZZ +ZG,ZGG +ZH,CSZ +ZJ,CJG +ZZ,ZZZ +WC,WCC +HO,DKH +SZ,SZZ +CJ,CJJ +XO,XOO +3Q,3QQ +WD,DSR +EY,ETD +9C,CQH +BK,OKA +MK,MKA +SN,BEL +GJ,CDC +OQ,CQN +O3,CSS +Z2,EZD +VD,VDD +GE,TNA +XF,VLK +QF,QFA +QW,QDA +QG,CTV +SQ,SIA +GX,CBG +XW,NCT +KJ,AIH +D7,XAX +AZ,AZA +TR,TGW +5K,HFY +DB,MLT +6W,FBS +8M,MMA +FP,PVV +NZ,ANZ +V8,VAS +BO,KLJ +RS,ASV +B3,BLV +5B,BSX +A6,OTC +GI,LHA +GT,CGH +HT,CTJ +LT,SNG +VN,HVN +ZC,RSN +ZA,SWM +RY,CJX +JR,JOY +AQ,JYH +9H,CGN +E3,VGO +MZ,MZT +GA,GIA +DR,RLH +UQ,CUH +GD,GSC +CN,GDC +FU,FZA +TV,TBA +DL,DAL +UO,HKE +DZ,EPA +1M,AEB +BJ,BJN +KN,CUA +FE,FEA +KA,HDA +CG,HZX +JF,JAA +KL,KLM +K9,TSP +B7,UIA +VI,VDA +ZF,ZFH +B8,B80 +NS,HBH +CI,CAL +7C,JJA +TZ,SCO +LH,DLH +FA,FAV +G5,HXA +RA,RAA +8C,DXH +JD,CBJ +EJ,EJM +YV,YVA +CQ,MAJ +ES,ESL +UN,UNN +5X,UPS +RU,RUS +CK,CKK +GS,GCR +BH,BHE +UA,UUA +HX,CRK +VJ,VJC +J5,JJ5 +KY,KNA +AE,MDA +LJ,JNA +CF,CYZ +BX,ABL +EF,DDD +Y8,YZR +PN,CHB +BG,ADB +YL,YAL +NW,VIA +IJ,GWL +UW,UTP +JH,JHH +JS,KOR +5Y,GTI +FR,FRT +3U,CSC +AB,ABD +CA,CCA +KK,CFZ +CX,CPA +CZ,CSN +DE,DER +EU,UEA +FD,CFA +FM,CSH +FX,FDX +GP,CTH +GY,CGZ +HA,HAH +HC,CHC +HK,EAS +HR,CU2 +HU,CHH +JL,JAL +KE,KAL +LX,LXH +MF,CXA +MH,MAS +MI,SLK +MU,CES +N2,MGG +NH,ANA +NX,AMU +OM,OMA +OZ,AAR +K4,CKS +8Y,AAV +BR,EVA +3V,TAY +YG,HYT +JB,JBA +T8,TXC +I9,HLF +AY,FIN +N8,NCR +8S,GTR +JG,JDL +VZ,TVJ +DV,VSV +FV,SDM +IA,IAW +RD,RDA +SE,URO +TE,IGA +H9,HIM +SL,TLM +C6,MFX +4B,TUP +U6,SVR +AF,AFR +MR,861 +IO,IAE +RF,EOK diff --git a/命令.md b/命令.md index 4c777b6..5d6e792 100644 --- a/命令.md +++ b/命令.md @@ -1,5 +1,6 @@ ### 命令 ``` +#编译 mvn -pl qaup-admin -am clean package -DskipTests ```