|
|
@@ -16,6 +16,13 @@ public class Grp_AirTicketReservations : EntityBase
|
|
|
[SugarColumn(IsNullable = true, ColumnDataType = "int")]
|
|
|
public int DIId { get; set; }
|
|
|
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 数据标识:0-旧数据 1-新数据
|
|
|
+ /// </summary>
|
|
|
+ [SugarColumn(IsNullable = true, ColumnDataType = "int")]
|
|
|
+ public int IsNew { get; set; }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 记录类型:0-正常机票 1-退票记录
|
|
|
/// </summary>
|
|
|
@@ -452,18 +459,10 @@ public class FlightInfo
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
-/// 航班解析器
|
|
|
-/// 支持格式:
|
|
|
-/// 1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
-/// 2.LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M
|
|
|
-/// 3.LH2440 TH29MAY MUCCPH 1025 1200 2 2 32A 01H35M
|
|
|
-/// 4.CA878 SA31MAY CPHPEK 1905 1000+1 3 T3 359 08H55M
|
|
|
+/// 航班解析器 - 支持多种格式
|
|
|
/// </summary>
|
|
|
public static class FlightParser
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// 月份映射表
|
|
|
- /// </summary>
|
|
|
private static readonly Dictionary<string, int> MonthMap = new()
|
|
|
{
|
|
|
["JAN"] = 1,
|
|
|
@@ -480,10 +479,6 @@ public static class FlightParser
|
|
|
["DEC"] = 12
|
|
|
};
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 解析航班信息
|
|
|
- /// 自动推断年份
|
|
|
- /// </summary>
|
|
|
public static List<FlightInfo> ParseFlights(string text)
|
|
|
{
|
|
|
var result = new List<FlightInfo>();
|
|
|
@@ -491,11 +486,7 @@ public static class FlightParser
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
|
return result;
|
|
|
|
|
|
- // 支持:
|
|
|
- // 1.CA431 ... 2.LH098 ...
|
|
|
- // 或
|
|
|
- // 1.CA431 ...
|
|
|
- // 2.LH098 ...
|
|
|
+ // 匹配所有以数字开头的行
|
|
|
var matches = Regex.Matches(
|
|
|
text,
|
|
|
@"\d+\..*?(?=\d+\.|$)",
|
|
|
@@ -504,31 +495,21 @@ public static class FlightParser
|
|
|
foreach (Match match in matches)
|
|
|
{
|
|
|
var flight = ParseLine(match.Value.Trim());
|
|
|
-
|
|
|
if (flight != null)
|
|
|
{
|
|
|
result.Add(flight);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 按序号排序
|
|
|
- result = result
|
|
|
- .OrderBy(x => x.SequenceNo)
|
|
|
- .ToList();
|
|
|
-
|
|
|
- // 自动修正跨年
|
|
|
+ result = result.OrderBy(x => x.SequenceNo).ToList();
|
|
|
FixYear(result);
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 解析单条航班
|
|
|
- /// </summary>
|
|
|
private static FlightInfo ParseLine(string line)
|
|
|
{
|
|
|
var seqMatch = Regex.Match(line, @"^(\d+)\.");
|
|
|
-
|
|
|
if (!seqMatch.Success)
|
|
|
return null;
|
|
|
|
|
|
@@ -539,136 +520,179 @@ public static class FlightParser
|
|
|
};
|
|
|
|
|
|
string content = line.Substring(seqMatch.Length).Trim();
|
|
|
+ var parts = Regex.Split(content, @"\s+")
|
|
|
+ .Where(p => !string.IsNullOrWhiteSpace(p))
|
|
|
+ .ToList();
|
|
|
|
|
|
- var parts = Regex.Split(content, @"\s+");
|
|
|
-
|
|
|
- if (parts.Length < 5)
|
|
|
+ if (parts.Count < 5)
|
|
|
return null;
|
|
|
|
|
|
int idx = 0;
|
|
|
|
|
|
- // 航班号
|
|
|
+ // 1. 航班号
|
|
|
flight.FlightNumber = parts[idx++];
|
|
|
|
|
|
- // 日期
|
|
|
+ // 2. 跳过可选的舱位代码(单个大写字母)
|
|
|
+ if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]$"))
|
|
|
+ {
|
|
|
+ idx++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 解析日期(格式:TU23JUN 或 TU27MAY)
|
|
|
+ if (idx >= parts.Count)
|
|
|
+ return null;
|
|
|
+
|
|
|
string dateText = parts[idx++];
|
|
|
|
|
|
- flight.WeekDay = dateText[..2];
|
|
|
+ // 提取星期和日期
|
|
|
+ var dateMatch = Regex.Match(dateText, @"^([A-Z]{2})(\d{2})([A-Z]{3})$");
|
|
|
+ if (dateMatch.Success)
|
|
|
+ {
|
|
|
+ flight.WeekDay = dateMatch.Groups[1].Value;
|
|
|
+ flight.DepartureDate = ParseDate(
|
|
|
+ int.Parse(dateMatch.Groups[2].Value),
|
|
|
+ dateMatch.Groups[3].Value
|
|
|
+ );
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 兼容旧格式:TU27MAY 直接解析
|
|
|
+ flight.WeekDay = dateText.Length >= 2 ? dateText[..2] : "";
|
|
|
+ flight.DepartureDate = ParseMonthDay(dateText);
|
|
|
+ }
|
|
|
|
|
|
- // 这里只解析月日
|
|
|
- flight.DepartureDate = ParseMonthDay(dateText);
|
|
|
+ // 4. 航线(6位:出发+到达)
|
|
|
+ if (idx >= parts.Count)
|
|
|
+ return null;
|
|
|
|
|
|
- // 航线
|
|
|
string route = parts[idx++];
|
|
|
-
|
|
|
if (route.Length >= 6)
|
|
|
{
|
|
|
flight.DepartureAirport = route[..3];
|
|
|
flight.ArrivalAirport = route.Substring(3, 3);
|
|
|
}
|
|
|
|
|
|
- // 起飞时间
|
|
|
+ // 5. 跳过预订状态码(如 HK4, HK, KL 等)
|
|
|
+ if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]{2}\d*$"))
|
|
|
+ {
|
|
|
+ idx++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 起飞时间
|
|
|
+ if (idx >= parts.Count)
|
|
|
+ return null;
|
|
|
flight.DepartureTime = FormatTime(parts[idx++]);
|
|
|
|
|
|
- // 到达时间
|
|
|
+ // 7. 到达时间
|
|
|
+ if (idx >= parts.Count)
|
|
|
+ return null;
|
|
|
+
|
|
|
string arrivalRaw = parts[idx++];
|
|
|
+ ParseArrivalTime(arrivalRaw, flight);
|
|
|
|
|
|
+ // 8. 跳过可选的舱位代码(如 E)
|
|
|
+ if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]$"))
|
|
|
+ {
|
|
|
+ idx++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 9. 解析剩余字段(航站楼、机型、飞行时长)
|
|
|
+ var remain = parts.Skip(idx).ToList();
|
|
|
+ ParseRemainFields(remain, flight);
|
|
|
+
|
|
|
+ return flight;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 解析到达时间(支持 +1 格式)
|
|
|
+ /// </summary>
|
|
|
+ private static void ParseArrivalTime(string arrivalRaw, FlightInfo flight)
|
|
|
+ {
|
|
|
if (arrivalRaw.Contains('+'))
|
|
|
{
|
|
|
var arr = arrivalRaw.Split('+');
|
|
|
-
|
|
|
flight.ArrivalTime = FormatTime(arr[0]);
|
|
|
-
|
|
|
flight.IsNextDayArrival = true;
|
|
|
-
|
|
|
- if (arr.Length > 1)
|
|
|
+ if (arr.Length > 1 && int.TryParse(arr[1], out int days))
|
|
|
{
|
|
|
- flight.NextDayCount = int.Parse(arr[1]);
|
|
|
+ flight.NextDayCount = days;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
flight.ArrivalTime = FormatTime(arrivalRaw);
|
|
|
}
|
|
|
-
|
|
|
- // 剩余字段
|
|
|
- var remain = parts.Skip(idx).ToList();
|
|
|
-
|
|
|
- ParseRemainFields(remain, flight);
|
|
|
-
|
|
|
- return flight;
|
|
|
}
|
|
|
|
|
|
- ///// <summary>
|
|
|
- /////
|
|
|
- ///// </summary>
|
|
|
- ///// <param name="remain">
|
|
|
- ///// 例如:
|
|
|
- ///// T1 1 359 11H10M
|
|
|
- ///// 1 2 321 00H55M
|
|
|
- ///// 3 T3 359 08H55M
|
|
|
- ///// </param>
|
|
|
- /////
|
|
|
-
|
|
|
/// <summary>
|
|
|
- /// 解析剩余字段
|
|
|
+ /// 解析剩余字段(航站楼、机型、飞行时长)
|
|
|
/// </summary>
|
|
|
- /// <param name="remain">
|
|
|
- /// 例如:
|
|
|
- /// T1 1 359 11H10M
|
|
|
- /// 1 2 321 00H55M
|
|
|
- /// 3 T3 359 08H55M
|
|
|
- /// </param>
|
|
|
- /// <param name="flight"></param>
|
|
|
- private static void ParseRemainFields(
|
|
|
- List<string> remain,
|
|
|
- FlightInfo flight)
|
|
|
+ private static void ParseRemainFields(List<string> remain, FlightInfo flight)
|
|
|
{
|
|
|
- if (remain.Count < 2)
|
|
|
+ if (remain.Count == 0)
|
|
|
return;
|
|
|
|
|
|
- // 最后一个字段一般是飞行时长
|
|
|
- if (Regex.IsMatch(remain[^1], @"^\d{2}H\d{2}M$"))
|
|
|
- {
|
|
|
- flight.Duration = remain[^1];
|
|
|
+ // 从后往前解析
|
|
|
+ int idx = remain.Count - 1;
|
|
|
|
|
|
- remain.RemoveAt(remain.Count - 1);
|
|
|
+ // 1. 最后一个字段:飞行时长(格式:11H10M 或 00H55M)
|
|
|
+ if (idx >= 0 && Regex.IsMatch(remain[idx], @"^\d{2}H\d{2}M$"))
|
|
|
+ {
|
|
|
+ flight.Duration = remain[idx];
|
|
|
+ idx--;
|
|
|
}
|
|
|
|
|
|
- // 倒数第二个字段一般是机型
|
|
|
- if (remain.Count > 0 &&
|
|
|
- Regex.IsMatch(remain[^1], @"^\d{2,3}[A-Z]?$"))
|
|
|
+ // 2. 倒数第二个字段:机型(格式:359, 321, 32A, 737 等)
|
|
|
+ if (idx >= 0 && Regex.IsMatch(remain[idx], @"^[A-Z]?\d{2,3}[A-Z]?$"))
|
|
|
{
|
|
|
- flight.AircraftType = remain[^1];
|
|
|
-
|
|
|
- remain.RemoveAt(remain.Count - 1);
|
|
|
+ flight.AircraftType = remain[idx];
|
|
|
+ idx--;
|
|
|
}
|
|
|
|
|
|
- // 剩余字段解析为航站楼
|
|
|
- if (remain.Count == 1)
|
|
|
+ // 3. 剩余字段解析航站楼
|
|
|
+ // 情况1:一个航站楼(出发和到达相同)
|
|
|
+ if (idx == 0)
|
|
|
{
|
|
|
- flight.DepartureTerminal =
|
|
|
- NormalizeTerminal(remain[0]);
|
|
|
+ flight.DepartureTerminal = NormalizeTerminal(remain[0]);
|
|
|
+ flight.ArrivalTerminal = flight.DepartureTerminal;
|
|
|
}
|
|
|
- else if (remain.Count >= 2)
|
|
|
+ // 情况2:两个航站楼(出发和到达不同)
|
|
|
+ else if (idx >= 1)
|
|
|
{
|
|
|
- flight.DepartureTerminal =
|
|
|
- NormalizeTerminal(remain[0]);
|
|
|
+ // 检查是否是两个航站楼
|
|
|
+ string terminal1 = remain[0];
|
|
|
+ string terminal2 = remain[1];
|
|
|
|
|
|
- flight.ArrivalTerminal =
|
|
|
- NormalizeTerminal(remain[1]);
|
|
|
+ // 如果两个都是航站楼格式(T1, T2, 1, 2, T3等)
|
|
|
+ if (IsTerminalFormat(terminal1) && IsTerminalFormat(terminal2))
|
|
|
+ {
|
|
|
+ flight.DepartureTerminal = NormalizeTerminal(terminal1);
|
|
|
+ flight.ArrivalTerminal = NormalizeTerminal(terminal2);
|
|
|
+ }
|
|
|
+ // 如果第一个是航站楼,第二个不是,则只解析第一个
|
|
|
+ else if (IsTerminalFormat(terminal1))
|
|
|
+ {
|
|
|
+ flight.DepartureTerminal = NormalizeTerminal(terminal1);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 判断是否为航站楼格式
|
|
|
+ /// </summary>
|
|
|
+ private static bool IsTerminalFormat(string value)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(value))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ value = value.Trim().ToUpper();
|
|
|
+ // 匹配:T1, T2, T3, 1, 2, 3 等
|
|
|
+ return Regex.IsMatch(value, @"^T?\d+$");
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 标准化航站楼
|
|
|
/// </summary>
|
|
|
- /// <param name="terminal">
|
|
|
- /// 原始值:1、2、 T3
|
|
|
- /// </param>
|
|
|
- /// <returns>
|
|
|
- /// T1、T2、T3
|
|
|
- /// </returns>
|
|
|
private static string NormalizeTerminal(string terminal)
|
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(terminal))
|
|
|
@@ -676,7 +700,7 @@ public static class FlightParser
|
|
|
|
|
|
terminal = terminal.Trim().ToUpper();
|
|
|
|
|
|
- // 纯数字补T
|
|
|
+ // 纯数字补 T
|
|
|
if (Regex.IsMatch(terminal, @"^\d+$"))
|
|
|
{
|
|
|
return $"T{terminal}";
|
|
|
@@ -687,8 +711,6 @@ public static class FlightParser
|
|
|
|
|
|
/// <summary>
|
|
|
/// 格式化时间
|
|
|
- /// 0135 -> 01:35
|
|
|
- /// 905 -> 09:05
|
|
|
/// </summary>
|
|
|
private static string FormatTime(string time)
|
|
|
{
|
|
|
@@ -710,17 +732,44 @@ public static class FlightParser
|
|
|
return time;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 解析日期
|
|
|
+ /// </summary>
|
|
|
+ private static DateTime ParseDate(int day, string month)
|
|
|
+ {
|
|
|
+ if (!MonthMap.TryGetValue(month.ToUpper(), out int monthNum))
|
|
|
+ return DateTime.MinValue;
|
|
|
+
|
|
|
+ return new DateTime(DateTime.Now.Year, monthNum, day);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 解析月日(兼容旧格式)
|
|
|
+ /// </summary>
|
|
|
+ private static DateTime ParseMonthDay(string value)
|
|
|
+ {
|
|
|
+ value = Regex.Replace(value, @"^[A-Z]{2}", "");
|
|
|
+
|
|
|
+ var match = Regex.Match(value, @"(\d{2})([A-Z]{3})");
|
|
|
+ if (!match.Success)
|
|
|
+ return DateTime.MinValue;
|
|
|
+
|
|
|
+ int day = int.Parse(match.Groups[1].Value);
|
|
|
+ if (!MonthMap.TryGetValue(match.Groups[2].Value, out int month))
|
|
|
+ return DateTime.MinValue;
|
|
|
+
|
|
|
+ return new DateTime(DateTime.Now.Year, month, day);
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 自动修正跨年
|
|
|
/// </summary>
|
|
|
- private static void FixYear(
|
|
|
- List<FlightInfo> flights)
|
|
|
+ private static void FixYear(List<FlightInfo> flights)
|
|
|
{
|
|
|
if (flights.Count <= 1)
|
|
|
return;
|
|
|
|
|
|
int currentYear = DateTime.Now.Year;
|
|
|
-
|
|
|
DateTime previousDate = DateTime.MinValue;
|
|
|
|
|
|
foreach (var flight in flights)
|
|
|
@@ -733,9 +782,7 @@ public static class FlightParser
|
|
|
flight.DepartureDate.Month,
|
|
|
flight.DepartureDate.Day);
|
|
|
|
|
|
- // 出现日期倒退
|
|
|
- if (previousDate != DateTime.MinValue &&
|
|
|
- date < previousDate)
|
|
|
+ if (previousDate != DateTime.MinValue && date < previousDate)
|
|
|
{
|
|
|
currentYear++;
|
|
|
date = new DateTime(
|
|
|
@@ -745,42 +792,7 @@ public static class FlightParser
|
|
|
}
|
|
|
|
|
|
flight.DepartureDate = date;
|
|
|
-
|
|
|
previousDate = date;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 解析月日
|
|
|
- /// TU27MAY -> 05-27
|
|
|
- /// </summary>
|
|
|
- private static DateTime ParseMonthDay(string value)
|
|
|
- {
|
|
|
- value = Regex.Replace(
|
|
|
- value,
|
|
|
- @"^[A-Z]{2}",
|
|
|
- "");
|
|
|
-
|
|
|
- var match = Regex.Match(
|
|
|
- value,
|
|
|
- @"(\d{2})([A-Z]{3})");
|
|
|
-
|
|
|
- if (!match.Success)
|
|
|
- return DateTime.MinValue;
|
|
|
-
|
|
|
- int day = int.Parse(match.Groups[1].Value);
|
|
|
-
|
|
|
- if (!MonthMap.TryGetValue(
|
|
|
- match.Groups[2].Value,
|
|
|
- out int month))
|
|
|
- {
|
|
|
- return DateTime.MinValue;
|
|
|
- }
|
|
|
-
|
|
|
- // 先使用当前年份
|
|
|
- return new DateTime(
|
|
|
- DateTime.Now.Year,
|
|
|
- month,
|
|
|
- day);
|
|
|
- }
|
|
|
}
|