14 changed files with 2518 additions and 651 deletions
@ -0,0 +1,121 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志信道配置
|
|||
/// </summary>
|
|||
public static class LogChannelConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 信道定义列表
|
|||
/// </summary>
|
|||
public static readonly List<ChannelDefinition> ChannelDefinitions = new() |
|||
{ |
|||
new() { Name = "PRACH", Color = "#ffff00" }, |
|||
new() { Name = "NPRACH", Color = "#ffff00" }, |
|||
new() { Name = "SRS", Color = "#ffff80" }, |
|||
new() { Name = "PUCCH", Color = "#00ff00" }, |
|||
new() { Name = "PUSCH", Color = "#ff0000" }, |
|||
new() { Name = "NPUSCH", Color = "#ff0000" }, |
|||
new() { Name = "PDSCH", Color = "#0000ff" }, |
|||
new() { Name = "NPDSCH", Color = "#0000ff" }, |
|||
new() { Name = "PDCCH", Color = "#00ffff" }, |
|||
new() { Name = "EPDCCH", Color = "#00ffff" }, |
|||
new() { Name = "NPDCCH", Color = "#00ffff" }, |
|||
new() { Name = "PMCH", Color = "#ff80ff" }, |
|||
new() { Name = "INV", Color = "#D0D0D0" } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// 信道定义字典(按名称索引)
|
|||
/// </summary>
|
|||
public static readonly Dictionary<string, ChannelDefinition> ChannelsByName = ChannelDefinitions |
|||
.ToDictionary(c => c.Name, c => c); |
|||
|
|||
/// <summary>
|
|||
/// 信道定义字典(按ID索引)
|
|||
/// </summary>
|
|||
public static readonly Dictionary<int, ChannelDefinition> ChannelsById = ChannelDefinitions |
|||
.Select((c, i) => new { Channel = c, Index = i }) |
|||
.ToDictionary(x => x.Index, x => x.Channel); |
|||
|
|||
/// <summary>
|
|||
/// 获取信道定义
|
|||
/// </summary>
|
|||
/// <param name="name">信道名称</param>
|
|||
/// <returns>信道定义</returns>
|
|||
public static ChannelDefinition? GetChannelByName(string name) |
|||
{ |
|||
return ChannelsByName.TryGetValue(name, out var channel) ? channel : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取信道定义
|
|||
/// </summary>
|
|||
/// <param name="id">信道ID</param>
|
|||
/// <returns>信道定义</returns>
|
|||
public static ChannelDefinition? GetChannelById(int id) |
|||
{ |
|||
return ChannelsById.TryGetValue(id, out var channel) ? channel : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有信道名称
|
|||
/// </summary>
|
|||
/// <returns>信道名称列表</returns>
|
|||
public static List<string> GetAllChannelNames() |
|||
{ |
|||
return ChannelDefinitions.Select(c => c.Name).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证信道名称是否有效
|
|||
/// </summary>
|
|||
/// <param name="name">信道名称</param>
|
|||
/// <returns>是否有效</returns>
|
|||
public static bool IsValidChannel(string name) |
|||
{ |
|||
return ChannelsByName.ContainsKey(name); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取信道ID
|
|||
/// </summary>
|
|||
/// <param name="name">信道名称</param>
|
|||
/// <returns>信道ID,如果不存在返回-1</returns>
|
|||
public static int GetChannelId(string name) |
|||
{ |
|||
for (int i = 0; i < ChannelDefinitions.Count; i++) |
|||
{ |
|||
if (ChannelDefinitions[i].Name == name) |
|||
return i; |
|||
} |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 信道定义
|
|||
/// </summary>
|
|||
public class ChannelDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// 信道名称
|
|||
/// </summary>
|
|||
public string Name { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 颜色代码
|
|||
/// </summary>
|
|||
public string Color { get; set; } = "#000000"; |
|||
|
|||
/// <summary>
|
|||
/// 信道ID
|
|||
/// </summary>
|
|||
public int Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 信道索引
|
|||
/// </summary>
|
|||
public int Index { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,254 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志常量
|
|||
/// </summary>
|
|||
public static class LogConstants |
|||
{ |
|||
#region 全局常量
|
|||
|
|||
/// <summary>
|
|||
/// 最大日志数量
|
|||
/// </summary>
|
|||
public const int LOGS_MAX = 2000000; |
|||
|
|||
/// <summary>
|
|||
/// 日志范围
|
|||
/// </summary>
|
|||
public static readonly List<long[]> LOGS_RANGE = new(); |
|||
|
|||
/// <summary>
|
|||
/// 日志信息
|
|||
/// </summary>
|
|||
public static readonly List<int> LOGS_INFO = new(); |
|||
|
|||
/// <summary>
|
|||
/// 日志层
|
|||
/// </summary>
|
|||
public static readonly List<string> LOGS_LAYERS = new(); |
|||
|
|||
/// <summary>
|
|||
/// 导出时间格式
|
|||
/// </summary>
|
|||
public static string EXPORT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.fff"; |
|||
|
|||
/// <summary>
|
|||
/// 是否启用内联缓存
|
|||
/// </summary>
|
|||
public static bool INLINE_CACHE_ENABLED = false; |
|||
|
|||
#endregion
|
|||
|
|||
#region 应用模式
|
|||
|
|||
/// <summary>
|
|||
/// 应用模式
|
|||
/// </summary>
|
|||
public static class AppMode |
|||
{ |
|||
public const string Web = "web"; |
|||
public const string App = "app"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 导出模式
|
|||
/// </summary>
|
|||
public static class ExportMode |
|||
{ |
|||
public const string Zip = "zip"; |
|||
public const string Text = "text"; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 字体和样式
|
|||
|
|||
/// <summary>
|
|||
/// 默认字体
|
|||
/// </summary>
|
|||
public const string DEFAULT_FONT = "13px helvetica, arial, verdana, sans-serif"; |
|||
|
|||
/// <summary>
|
|||
/// 默认颜色
|
|||
/// </summary>
|
|||
public const string DEFAULT_COLOR = "#000000"; |
|||
|
|||
#endregion
|
|||
|
|||
#region 时间相关
|
|||
|
|||
/// <summary>
|
|||
/// 时间戳基准(2000-01-01 00:00:00)
|
|||
/// </summary>
|
|||
public const long TIMESTAMP_BASE = 946681200000; |
|||
|
|||
/// <summary>
|
|||
/// 默认时间原点
|
|||
/// </summary>
|
|||
public const string DEFAULT_TIME_ORIGIN = "00:00:00.000"; |
|||
|
|||
#endregion
|
|||
|
|||
#region 客户端相关
|
|||
|
|||
/// <summary>
|
|||
/// 默认重连延迟(毫秒)
|
|||
/// </summary>
|
|||
public const int DEFAULT_RECONNECT_DELAY = 2500; |
|||
|
|||
/// <summary>
|
|||
/// 默认重连延迟(毫秒)
|
|||
/// </summary>
|
|||
public const int DEFAULT_RECONNECT_DELAY_APP = 5000; |
|||
|
|||
/// <summary>
|
|||
/// HFN回绕阈值
|
|||
/// </summary>
|
|||
public const int HFN_WRAP_THRESHOLD = 512; |
|||
|
|||
#endregion
|
|||
|
|||
#region 日志级别
|
|||
|
|||
/// <summary>
|
|||
/// 日志级别名称
|
|||
/// </summary>
|
|||
public static readonly string[] LOG_LEVEL_NAMES = { "none", "error", "warn", "info", "debug" }; |
|||
|
|||
/// <summary>
|
|||
/// 日志方法名称
|
|||
/// </summary>
|
|||
public static readonly string[] LOG_METHODS = { "error", "warn", "info", "debug", "prof" }; |
|||
|
|||
#endregion
|
|||
|
|||
#region 性能相关
|
|||
|
|||
/// <summary>
|
|||
/// 大日志列表阈值
|
|||
/// </summary>
|
|||
public const int LARGE_LOG_THRESHOLD = 500000; |
|||
|
|||
/// <summary>
|
|||
/// 小日志列表阈值
|
|||
/// </summary>
|
|||
public const int SMALL_LOG_THRESHOLD = 50000; |
|||
|
|||
/// <summary>
|
|||
/// 日志模块列表最大长度
|
|||
/// </summary>
|
|||
public const int LOG_MODULE_LIST_MAX = 20; |
|||
|
|||
#endregion
|
|||
|
|||
#region 文件相关
|
|||
|
|||
/// <summary>
|
|||
/// 支持的文件类型
|
|||
/// </summary>
|
|||
public static class FileTypes |
|||
{ |
|||
public const string Binary = "bin"; |
|||
public const string Base64 = "base64"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 文件大小单位
|
|||
/// </summary>
|
|||
public static class FileSizeUnits |
|||
{ |
|||
public const string Kilobyte = "K"; |
|||
public const string Megabyte = "M"; |
|||
public const string Gigabyte = "G"; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 正则表达式
|
|||
|
|||
/// <summary>
|
|||
/// 参数解析正则表达式
|
|||
/// </summary>
|
|||
public const string PARAMS_REGEX = @"\s*([^=]+)=([^,\s]+)"; |
|||
|
|||
#endregion
|
|||
|
|||
#region 事件相关
|
|||
|
|||
/// <summary>
|
|||
/// 事件类型
|
|||
/// </summary>
|
|||
public static class EventTypes |
|||
{ |
|||
public const string NewLogs = "newLogs"; |
|||
public const string SelectLog = "selectLog"; |
|||
public const string All = "*"; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 浏览器相关
|
|||
|
|||
/// <summary>
|
|||
/// 鼠标滚轮事件
|
|||
/// </summary>
|
|||
public static class MouseWheelEvents |
|||
{ |
|||
public const string Firefox = "DOMMouseScroll"; |
|||
public const string Other = "mousewheel"; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 工具方法
|
|||
|
|||
/// <summary>
|
|||
/// 获取日志级别名称
|
|||
/// </summary>
|
|||
/// <param name="level">级别值</param>
|
|||
/// <returns>级别名称</returns>
|
|||
public static string GetLogLevelName(int level) |
|||
{ |
|||
if (level >= 0 && level < LOG_LEVEL_NAMES.Length) |
|||
return LOG_LEVEL_NAMES[level]; |
|||
return "unknown"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取日志级别值
|
|||
/// </summary>
|
|||
/// <param name="levelName">级别名称</param>
|
|||
/// <returns>级别值</returns>
|
|||
public static int GetLogLevelValue(string levelName) |
|||
{ |
|||
for (int i = 0; i < LOG_LEVEL_NAMES.Length; i++) |
|||
{ |
|||
if (LOG_LEVEL_NAMES[i] == levelName) |
|||
return i; |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查是否为持续时间戳
|
|||
/// </summary>
|
|||
/// <param name="timestamp">时间戳</param>
|
|||
/// <returns>是否为持续时间</returns>
|
|||
public static bool IsDurationTimestamp(long timestamp) |
|||
{ |
|||
return timestamp < TIMESTAMP_BASE; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取默认重连延迟
|
|||
/// </summary>
|
|||
/// <param name="mode">应用模式</param>
|
|||
/// <returns>重连延迟</returns>
|
|||
public static int GetDefaultReconnectDelay(string mode) |
|||
{ |
|||
return mode == AppMode.App ? DEFAULT_RECONNECT_DELAY_APP : DEFAULT_RECONNECT_DELAY; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,74 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志传输方向常量
|
|||
/// </summary>
|
|||
public static class LogDirection |
|||
{ |
|||
/// <summary>
|
|||
/// 无方向
|
|||
/// </summary>
|
|||
public const int None = 0; |
|||
|
|||
/// <summary>
|
|||
/// 上行传输
|
|||
/// </summary>
|
|||
public const int UL = 1; |
|||
|
|||
/// <summary>
|
|||
/// 下行传输
|
|||
/// </summary>
|
|||
public const int DL = 2; |
|||
|
|||
/// <summary>
|
|||
/// 从某处传输
|
|||
/// </summary>
|
|||
public const int From = 3; |
|||
|
|||
/// <summary>
|
|||
/// 传输到某处
|
|||
/// </summary>
|
|||
public const int To = 4; |
|||
|
|||
/// <summary>
|
|||
/// 获取方向名称
|
|||
/// </summary>
|
|||
/// <param name="direction">方向值</param>
|
|||
/// <returns>方向名称</returns>
|
|||
public static string GetDirectionName(int direction) |
|||
{ |
|||
return direction switch |
|||
{ |
|||
None => "-", |
|||
UL => "UL", |
|||
DL => "DL", |
|||
From => "FROM", |
|||
To => "TO", |
|||
_ => "UNKNOWN" |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有方向选项
|
|||
/// </summary>
|
|||
/// <returns>方向选项列表</returns>
|
|||
public static List<DirectionOption> GetDirectionOptions() |
|||
{ |
|||
return new List<DirectionOption> |
|||
{ |
|||
new() { Value = None, Text = "-" }, |
|||
new() { Value = DL, Text = "DL" }, |
|||
new() { Value = UL, Text = "UL" } |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 方向选项
|
|||
/// </summary>
|
|||
public class DirectionOption |
|||
{ |
|||
public int Value { get; set; } |
|||
public string Text { get; set; } = string.Empty; |
|||
} |
|||
} |
@ -0,0 +1,229 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志事件管理器
|
|||
/// </summary>
|
|||
public class LogEventManager |
|||
{ |
|||
/// <summary>
|
|||
/// 事件监听器字典
|
|||
/// </summary>
|
|||
private readonly Dictionary<int, EventListener> _eventListeners = new(); |
|||
|
|||
/// <summary>
|
|||
/// 事件监听器ID计数器
|
|||
/// </summary>
|
|||
private int _eventListenerId = 0; |
|||
|
|||
/// <summary>
|
|||
/// 注册事件监听器
|
|||
/// </summary>
|
|||
/// <param name="type">事件类型</param>
|
|||
/// <param name="callback">回调函数</param>
|
|||
/// <returns>监听器ID</returns>
|
|||
public int RegisterEventListener(string type, Action<LogEvent> callback) |
|||
{ |
|||
var id = _eventListenerId++; |
|||
_eventListeners[id] = new EventListener |
|||
{ |
|||
Type = type, |
|||
Callback = callback |
|||
}; |
|||
return id; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 注销事件监听器
|
|||
/// </summary>
|
|||
/// <param name="id">监听器ID</param>
|
|||
public void UnregisterEventListener(int id) |
|||
{ |
|||
_eventListeners.Remove(id); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 发送事件
|
|||
/// </summary>
|
|||
/// <param name="event">事件对象</param>
|
|||
public void SendEvent(LogEvent @event) |
|||
{ |
|||
var type = @event.Type; |
|||
foreach (var kvp in _eventListeners) |
|||
{ |
|||
var listener = kvp.Value; |
|||
switch (listener.Type) |
|||
{ |
|||
case var t when t == type: |
|||
case LogConstants.EventTypes.All: |
|||
listener.Callback(@event); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 发送新日志事件
|
|||
/// </summary>
|
|||
/// <param name="logs">日志列表</param>
|
|||
public void SendNewLogsEvent(List<LTELog> logs) |
|||
{ |
|||
SendEvent(new LogEvent |
|||
{ |
|||
Type = LogConstants.EventTypes.NewLogs, |
|||
Data = new { Logs = logs } |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 发送选择日志事件
|
|||
/// </summary>
|
|||
/// <param name="log">选中的日志</param>
|
|||
public void SendSelectLogEvent(LTELog log) |
|||
{ |
|||
SendEvent(new LogEvent |
|||
{ |
|||
Type = LogConstants.EventTypes.SelectLog, |
|||
Data = new { Log = log } |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 清除所有事件监听器
|
|||
/// </summary>
|
|||
public void ClearAllListeners() |
|||
{ |
|||
_eventListeners.Clear(); |
|||
_eventListenerId = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取监听器数量
|
|||
/// </summary>
|
|||
/// <returns>监听器数量</returns>
|
|||
public int GetListenerCount() |
|||
{ |
|||
return _eventListeners.Count; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定类型的事件监听器数量
|
|||
/// </summary>
|
|||
/// <param name="type">事件类型</param>
|
|||
/// <returns>监听器数量</returns>
|
|||
public int GetListenerCountByType(string type) |
|||
{ |
|||
return _eventListeners.Values.Count(l => l.Type == type || l.Type == LogConstants.EventTypes.All); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 事件监听器
|
|||
/// </summary>
|
|||
public class EventListener |
|||
{ |
|||
/// <summary>
|
|||
/// 事件类型
|
|||
/// </summary>
|
|||
public string Type { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 回调函数
|
|||
/// </summary>
|
|||
public Action<LogEvent> Callback { get; set; } = _ => { }; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 日志事件
|
|||
/// </summary>
|
|||
public class LogEvent |
|||
{ |
|||
/// <summary>
|
|||
/// 事件类型
|
|||
/// </summary>
|
|||
public string Type { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 事件数据
|
|||
/// </summary>
|
|||
public object? Data { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 时间戳
|
|||
/// </summary>
|
|||
public DateTime Timestamp { get; set; } = DateTime.Now; |
|||
|
|||
/// <summary>
|
|||
/// 事件源
|
|||
/// </summary>
|
|||
public string? Source { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 日志事件类型
|
|||
/// </summary>
|
|||
public static class LogEventTypes |
|||
{ |
|||
/// <summary>
|
|||
/// 新日志事件
|
|||
/// </summary>
|
|||
public const string NewLogs = "newLogs"; |
|||
|
|||
/// <summary>
|
|||
/// 选择日志事件
|
|||
/// </summary>
|
|||
public const string SelectLog = "selectLog"; |
|||
|
|||
/// <summary>
|
|||
/// 过滤器更新事件
|
|||
/// </summary>
|
|||
public const string FilterUpdate = "filterUpdate"; |
|||
|
|||
/// <summary>
|
|||
/// 客户端状态变化事件
|
|||
/// </summary>
|
|||
public const string ClientStateChange = "clientStateChange"; |
|||
|
|||
/// <summary>
|
|||
/// 日志清除事件
|
|||
/// </summary>
|
|||
public const string LogsCleared = "logsCleared"; |
|||
|
|||
/// <summary>
|
|||
/// 错误事件
|
|||
/// </summary>
|
|||
public const string Error = "error"; |
|||
|
|||
/// <summary>
|
|||
/// 警告事件
|
|||
/// </summary>
|
|||
public const string Warning = "warning"; |
|||
|
|||
/// <summary>
|
|||
/// 信息事件
|
|||
/// </summary>
|
|||
public const string Info = "info"; |
|||
|
|||
/// <summary>
|
|||
/// 调试事件
|
|||
/// </summary>
|
|||
public const string Debug = "debug"; |
|||
|
|||
/// <summary>
|
|||
/// 所有事件
|
|||
/// </summary>
|
|||
public const string All = "*"; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有事件类型
|
|||
/// </summary>
|
|||
/// <returns>事件类型列表</returns>
|
|||
public static List<string> GetAllEventTypes() |
|||
{ |
|||
return new List<string> |
|||
{ |
|||
NewLogs, SelectLog, FilterUpdate, ClientStateChange, LogsCleared, |
|||
Error, Warning, Info, Debug, All |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,270 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志过滤器配置
|
|||
/// </summary>
|
|||
public class LogFilterConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 全局UE ID列表
|
|||
/// </summary>
|
|||
public List<int> GlobalUeIds { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 小区ID列表
|
|||
/// </summary>
|
|||
public List<int> CellIds { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// IMSI列表
|
|||
/// </summary>
|
|||
public List<string> ImsiList { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// IMEI列表
|
|||
/// </summary>
|
|||
public List<string> ImeiList { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 信息类型列表
|
|||
/// </summary>
|
|||
public List<int> InfoTypes { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 层类型列表
|
|||
/// </summary>
|
|||
public List<string> LayerTypes { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 日志级别列表
|
|||
/// </summary>
|
|||
public List<int> LogLevels { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 方向过滤器(false表示过滤掉,true表示保留)
|
|||
/// </summary>
|
|||
public bool[] DirectionFilter { get; set; } = { false, false, false }; |
|||
|
|||
/// <summary>
|
|||
/// 开始时间戳
|
|||
/// </summary>
|
|||
public long StartTime { get; set; } = 0; |
|||
|
|||
/// <summary>
|
|||
/// 结束时间戳
|
|||
/// </summary>
|
|||
public long EndTime { get; set; } = long.MaxValue; |
|||
|
|||
/// <summary>
|
|||
/// 时间原点
|
|||
/// </summary>
|
|||
public string TimeOrigin { get; set; } = "00:00:00.000"; |
|||
|
|||
/// <summary>
|
|||
/// 是否分组UE ID
|
|||
/// </summary>
|
|||
public bool GroupUeId { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// 搜索文本
|
|||
/// </summary>
|
|||
public string? SearchText { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否使用正则表达式搜索
|
|||
/// </summary>
|
|||
public bool UseRegexSearch { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 是否忽略大小写
|
|||
/// </summary>
|
|||
public bool IgnoreCase { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// 是否只显示错误
|
|||
/// </summary>
|
|||
public bool ShowErrorsOnly { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 是否只显示警告
|
|||
/// </summary>
|
|||
public bool ShowWarningsOnly { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 客户端过滤器
|
|||
/// </summary>
|
|||
public Dictionary<string, bool> ClientFilters { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 是否启用过滤器
|
|||
/// </summary>
|
|||
public bool IsEnabled { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// 过滤器名称
|
|||
/// </summary>
|
|||
public string? FilterName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否持久化
|
|||
/// </summary>
|
|||
public bool IsPersistent { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreatedAt { get; set; } = DateTime.Now; |
|||
|
|||
/// <summary>
|
|||
/// 更新时间
|
|||
/// </summary>
|
|||
public DateTime UpdatedAt { get; set; } = DateTime.Now; |
|||
|
|||
/// <summary>
|
|||
/// 清除所有过滤器
|
|||
/// </summary>
|
|||
public void ClearAll() |
|||
{ |
|||
GlobalUeIds.Clear(); |
|||
CellIds.Clear(); |
|||
ImsiList.Clear(); |
|||
ImeiList.Clear(); |
|||
InfoTypes.Clear(); |
|||
LayerTypes.Clear(); |
|||
LogLevels.Clear(); |
|||
DirectionFilter = new bool[] { false, false, false }; |
|||
StartTime = 0; |
|||
EndTime = long.MaxValue; |
|||
TimeOrigin = "00:00:00.000"; |
|||
SearchText = null; |
|||
ClientFilters.Clear(); |
|||
UpdatedAt = DateTime.Now; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 重置为默认值
|
|||
/// </summary>
|
|||
public void ResetToDefault() |
|||
{ |
|||
ClearAll(); |
|||
GroupUeId = true; |
|||
IsEnabled = true; |
|||
UseRegexSearch = false; |
|||
IgnoreCase = true; |
|||
ShowErrorsOnly = false; |
|||
ShowWarningsOnly = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 复制过滤器配置
|
|||
/// </summary>
|
|||
/// <returns>新的过滤器配置</returns>
|
|||
public LogFilterConfig Clone() |
|||
{ |
|||
return new LogFilterConfig |
|||
{ |
|||
GlobalUeIds = new List<int>(GlobalUeIds), |
|||
CellIds = new List<int>(CellIds), |
|||
ImsiList = new List<string>(ImsiList), |
|||
ImeiList = new List<string>(ImeiList), |
|||
InfoTypes = new List<int>(InfoTypes), |
|||
LayerTypes = new List<string>(LayerTypes), |
|||
LogLevels = new List<int>(LogLevels), |
|||
DirectionFilter = (bool[])DirectionFilter.Clone(), |
|||
StartTime = StartTime, |
|||
EndTime = EndTime, |
|||
TimeOrigin = TimeOrigin, |
|||
GroupUeId = GroupUeId, |
|||
SearchText = SearchText, |
|||
UseRegexSearch = UseRegexSearch, |
|||
IgnoreCase = IgnoreCase, |
|||
ShowErrorsOnly = ShowErrorsOnly, |
|||
ShowWarningsOnly = ShowWarningsOnly, |
|||
ClientFilters = new Dictionary<string, bool>(ClientFilters), |
|||
IsEnabled = IsEnabled, |
|||
FilterName = FilterName, |
|||
IsPersistent = IsPersistent, |
|||
CreatedAt = CreatedAt, |
|||
UpdatedAt = DateTime.Now |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查是否为空过滤器
|
|||
/// </summary>
|
|||
/// <returns>是否为空</returns>
|
|||
public bool IsEmpty() |
|||
{ |
|||
return GlobalUeIds.Count == 0 && |
|||
CellIds.Count == 0 && |
|||
ImsiList.Count == 0 && |
|||
ImeiList.Count == 0 && |
|||
InfoTypes.Count == 0 && |
|||
LayerTypes.Count == 0 && |
|||
LogLevels.Count == 0 && |
|||
DirectionFilter.All(x => !x) && |
|||
StartTime == 0 && |
|||
EndTime == long.MaxValue && |
|||
string.IsNullOrEmpty(SearchText) && |
|||
ClientFilters.Count == 0 && |
|||
!ShowErrorsOnly && |
|||
!ShowWarningsOnly; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 过滤器选项
|
|||
/// </summary>
|
|||
public class FilterOption |
|||
{ |
|||
/// <summary>
|
|||
/// 值
|
|||
/// </summary>
|
|||
public object Value { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 显示文本
|
|||
/// </summary>
|
|||
public string Text { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 样式
|
|||
/// </summary>
|
|||
public string Style { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 是否被选中
|
|||
/// </summary>
|
|||
public bool IsSelected { get; set; } = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 过滤器类型
|
|||
/// </summary>
|
|||
public static class FilterTypes |
|||
{ |
|||
public const string GlobalUeId = "global_ue_id"; |
|||
public const string Cell = "cell"; |
|||
public const string Imsi = "imsi"; |
|||
public const string Imei = "imei"; |
|||
public const string Info = "info"; |
|||
public const string Layer = "layer"; |
|||
public const string Level = "level"; |
|||
public const string Direction = "dir"; |
|||
public const string Time = "time"; |
|||
public const string Search = "search"; |
|||
public const string Client = "client"; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有过滤器类型
|
|||
/// </summary>
|
|||
/// <returns>过滤器类型列表</returns>
|
|||
public static List<string> GetAllFilterTypes() |
|||
{ |
|||
return new List<string> |
|||
{ |
|||
GlobalUeId, Cell, Imsi, Imei, Info, Layer, Level, Direction, Time, Search, Client |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,121 @@ |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志模型配置
|
|||
/// </summary>
|
|||
public static class LogModelConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 模型列表(顺序重要)
|
|||
/// </summary>
|
|||
public static readonly string[] ModelList = |
|||
{ |
|||
"RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// 模型配置字典
|
|||
/// </summary>
|
|||
public static readonly Dictionary<string, ModelConfig> Models = new() |
|||
{ |
|||
["RUE"] = new() { Icon = "icon-ue2" }, |
|||
["UE"] = new() { Icon = "icon-ue" }, |
|||
["PROBE"] = new() { }, |
|||
["ENB"] = new() |
|||
{ |
|||
Icon = "icon-air", |
|||
ModelHint = new Regex(@"enb|gnb|bbu", RegexOptions.IgnoreCase), |
|||
Title = "RAN" |
|||
}, |
|||
["N3IWF"] = new() |
|||
{ |
|||
Icon = "icon-air", |
|||
ModelHint = new Regex(@"n3iwf", RegexOptions.IgnoreCase), |
|||
Title = "N3IWF" |
|||
}, |
|||
["MME"] = new() |
|||
{ |
|||
Icon = "icon-server", |
|||
ModelHint = new Regex(@"epc|mme|amf", RegexOptions.IgnoreCase), |
|||
Title = "CN" |
|||
}, |
|||
["MBMSGW"] = new() |
|||
{ |
|||
Icon = "icon-download", |
|||
ModelHint = new Regex(@"mbms", RegexOptions.IgnoreCase) |
|||
}, |
|||
["IMS"] = new() { Icon = "icon-dial" }, |
|||
["MONITOR"] = new() { Icon = "icon-monitor" }, |
|||
["LICENSE"] = new() { Icon = "icon-file" } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// 获取模型配置
|
|||
/// </summary>
|
|||
/// <param name="modelName">模型名称</param>
|
|||
/// <returns>模型配置</returns>
|
|||
public static ModelConfig? GetModelConfig(string modelName) |
|||
{ |
|||
return Models.TryGetValue(modelName, out var config) ? config : null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 根据提示匹配模型
|
|||
/// </summary>
|
|||
/// <param name="hint">提示文本</param>
|
|||
/// <returns>匹配的模型名称</returns>
|
|||
public static string? MatchModelByHint(string hint) |
|||
{ |
|||
foreach (var kvp in Models) |
|||
{ |
|||
if (kvp.Value.ModelHint?.IsMatch(hint) == true) |
|||
{ |
|||
return kvp.Key; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有模型名称
|
|||
/// </summary>
|
|||
/// <returns>模型名称列表</returns>
|
|||
public static List<string> GetAllModelNames() |
|||
{ |
|||
return ModelList.ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证模型名称是否有效
|
|||
/// </summary>
|
|||
/// <param name="modelName">模型名称</param>
|
|||
/// <returns>是否有效</returns>
|
|||
public static bool IsValidModel(string modelName) |
|||
{ |
|||
return Models.ContainsKey(modelName); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 模型配置
|
|||
/// </summary>
|
|||
public class ModelConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 图标类名
|
|||
/// </summary>
|
|||
public string? Icon { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 模型提示正则表达式
|
|||
/// </summary>
|
|||
public Regex? ModelHint { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 显示标题
|
|||
/// </summary>
|
|||
public string? Title { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,314 @@ |
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志状态管理类
|
|||
/// </summary>
|
|||
public class LogState |
|||
{ |
|||
/// <summary>
|
|||
/// 日志列表
|
|||
/// </summary>
|
|||
public List<LTELog> Logs { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 客户端列表
|
|||
/// </summary>
|
|||
public List<LTEClient> ClientList { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 新日志列表
|
|||
/// </summary>
|
|||
public List<LTELog> NewLogs { get; set; } = new(); |
|||
|
|||
/// <summary>
|
|||
/// 是否重置元数据
|
|||
/// </summary>
|
|||
public bool ResetMetadata { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 检测到的最小日志级别
|
|||
/// </summary>
|
|||
public int MinLevelDetected { get; set; } = 10; |
|||
|
|||
/// <summary>
|
|||
/// 是否启用过滤器
|
|||
/// </summary>
|
|||
public bool Filter { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 是否更新网格
|
|||
/// </summary>
|
|||
public bool UpdateGrid { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 是否跟随最新日志
|
|||
/// </summary>
|
|||
public bool Follow { get; set; } = false; |
|||
|
|||
/// <summary>
|
|||
/// 过滤器锁定状态
|
|||
/// </summary>
|
|||
public string FilterLock { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 日志总数
|
|||
/// </summary>
|
|||
public int LogCount => Logs.Count; |
|||
|
|||
/// <summary>
|
|||
/// 客户端总数
|
|||
/// </summary>
|
|||
public int ClientCount => ClientList.Count; |
|||
|
|||
/// <summary>
|
|||
/// 新日志数量
|
|||
/// </summary>
|
|||
public int NewLogCount => NewLogs.Count; |
|||
|
|||
/// <summary>
|
|||
/// 启用的客户端数量
|
|||
/// </summary>
|
|||
public int EnabledClientCount => ClientList.Count(c => c.State == ClientState.Connected || c.State == ClientState.Start); |
|||
|
|||
/// <summary>
|
|||
/// 添加日志
|
|||
/// </summary>
|
|||
/// <param name="log">日志对象</param>
|
|||
public void AddLog(LTELog log) |
|||
{ |
|||
Logs.Add(log); |
|||
NewLogs.Add(log); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 添加客户端
|
|||
/// </summary>
|
|||
/// <param name="client">客户端对象</param>
|
|||
public void AddClient(LTEClient client) |
|||
{ |
|||
ClientList.Add(client); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 移除客户端
|
|||
/// </summary>
|
|||
/// <param name="client">客户端对象</param>
|
|||
/// <returns>是否成功移除</returns>
|
|||
public bool RemoveClient(LTEClient client) |
|||
{ |
|||
var removed = ClientList.Remove(client); |
|||
if (removed) |
|||
{ |
|||
// 移除该客户端的日志
|
|||
Logs.RemoveAll(log => log.Client == client); |
|||
NewLogs.RemoveAll(log => log.Client == client); |
|||
} |
|||
return removed; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 清除所有日志
|
|||
/// </summary>
|
|||
public void ClearLogs() |
|||
{ |
|||
Logs.Clear(); |
|||
NewLogs.Clear(); |
|||
ResetMetadata = true; |
|||
UpdateGrid = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 清除新日志
|
|||
/// </summary>
|
|||
public void ClearNewLogs() |
|||
{ |
|||
NewLogs.Clear(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 重置状态
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
Logs.Clear(); |
|||
NewLogs.Clear(); |
|||
ResetMetadata = true; |
|||
MinLevelDetected = 10; |
|||
Filter = false; |
|||
UpdateGrid = false; |
|||
Follow = false; |
|||
FilterLock = string.Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定客户端的日志
|
|||
/// </summary>
|
|||
/// <param name="client">客户端对象</param>
|
|||
/// <returns>日志列表</returns>
|
|||
public List<LTELog> GetLogsByClient(LTEClient client) |
|||
{ |
|||
return Logs.Where(log => log.Client == client).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取启用的客户端
|
|||
/// </summary>
|
|||
/// <returns>启用的客户端列表</returns>
|
|||
public List<LTEClient> GetEnabledClients() |
|||
{ |
|||
return ClientList.Where(c => c.State == ClientState.Connected || c.State == ClientState.Start).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取错误状态的客户端
|
|||
/// </summary>
|
|||
/// <returns>错误状态的客户端列表</returns>
|
|||
public List<LTEClient> GetErrorClients() |
|||
{ |
|||
return ClientList.Where(c => c.State == ClientState.Error).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取连接状态的客户端
|
|||
/// </summary>
|
|||
/// <returns>连接状态的客户端列表</returns>
|
|||
public List<LTEClient> GetConnectedClients() |
|||
{ |
|||
return ClientList.Where(c => c.State == ClientState.Connected).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查是否有实时客户端
|
|||
/// </summary>
|
|||
/// <returns>是否有实时客户端</returns>
|
|||
public bool HasRealTimeClients() |
|||
{ |
|||
return ClientList.Any(c => c.State == ClientState.Connected); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取日志统计信息
|
|||
/// </summary>
|
|||
/// <returns>统计信息</returns>
|
|||
public LogStatistics GetStatistics() |
|||
{ |
|||
return new LogStatistics |
|||
{ |
|||
TotalLogs = LogCount, |
|||
NewLogs = NewLogCount, |
|||
TotalClients = ClientCount, |
|||
EnabledClients = EnabledClientCount, |
|||
ConnectedClients = GetConnectedClients().Count, |
|||
ErrorClients = GetErrorClients().Count, |
|||
MinLevel = MinLevelDetected, |
|||
HasRealTime = HasRealTimeClients() |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 更新最小日志级别
|
|||
/// </summary>
|
|||
/// <param name="level">日志级别</param>
|
|||
public void UpdateMinLevel(int level) |
|||
{ |
|||
if (level < MinLevelDetected) |
|||
{ |
|||
MinLevelDetected = level; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置过滤器状态
|
|||
/// </summary>
|
|||
/// <param name="enabled">是否启用</param>
|
|||
public void SetFilter(bool enabled) |
|||
{ |
|||
Filter = enabled; |
|||
if (enabled) |
|||
{ |
|||
UpdateGrid = true; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 锁定过滤器
|
|||
/// </summary>
|
|||
/// <param name="lockName">锁定名称</param>
|
|||
public void LockFilter(string lockName) |
|||
{ |
|||
FilterLock = lockName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解锁过滤器
|
|||
/// </summary>
|
|||
public void UnlockFilter() |
|||
{ |
|||
FilterLock = string.Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查过滤器是否被锁定
|
|||
/// </summary>
|
|||
/// <returns>是否被锁定</returns>
|
|||
public bool IsFilterLocked() |
|||
{ |
|||
return !string.IsNullOrEmpty(FilterLock); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 日志统计信息
|
|||
/// </summary>
|
|||
public class LogStatistics |
|||
{ |
|||
/// <summary>
|
|||
/// 总日志数
|
|||
/// </summary>
|
|||
public int TotalLogs { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 新日志数
|
|||
/// </summary>
|
|||
public int NewLogs { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 总客户端数
|
|||
/// </summary>
|
|||
public int TotalClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 启用的客户端数
|
|||
/// </summary>
|
|||
public int EnabledClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 连接的客户端数
|
|||
/// </summary>
|
|||
public int ConnectedClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 错误的客户端数
|
|||
/// </summary>
|
|||
public int ErrorClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 最小日志级别
|
|||
/// </summary>
|
|||
public int MinLevel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否有实时客户端
|
|||
/// </summary>
|
|||
public bool HasRealTime { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 获取统计信息字符串
|
|||
/// </summary>
|
|||
/// <returns>统计信息字符串</returns>
|
|||
public override string ToString() |
|||
{ |
|||
return $"日志: {TotalLogs} (新: {NewLogs}), 客户端: {EnabledClients}/{TotalClients} (连接: {ConnectedClients}, 错误: {ErrorClients}), 最小级别: {MinLevel}"; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,331 @@ |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace LTEMvcApp.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 日志工具类
|
|||
/// </summary>
|
|||
public static class LogUtils |
|||
{ |
|||
/// <summary>
|
|||
/// 日志级别常量
|
|||
/// </summary>
|
|||
public static class LogLevels |
|||
{ |
|||
public const int None = 0; |
|||
public const int Error = 1; |
|||
public const int Warn = 2; |
|||
public const int Info = 3; |
|||
public const int Debug = 4; |
|||
|
|||
/// <summary>
|
|||
/// 获取级别名称
|
|||
/// </summary>
|
|||
/// <param name="level">级别值</param>
|
|||
/// <returns>级别名称</returns>
|
|||
public static string GetLevelName(int level) |
|||
{ |
|||
return level switch |
|||
{ |
|||
None => "none", |
|||
Error => "error", |
|||
Warn => "warn", |
|||
Info => "info", |
|||
Debug => "debug", |
|||
_ => "unknown" |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取级别值
|
|||
/// </summary>
|
|||
/// <param name="levelName">级别名称</param>
|
|||
/// <returns>级别值</returns>
|
|||
public static int GetLevelValue(string levelName) |
|||
{ |
|||
return levelName?.ToLower() switch |
|||
{ |
|||
"none" => None, |
|||
"error" => Error, |
|||
"warn" => Warn, |
|||
"info" => Info, |
|||
"debug" => Debug, |
|||
_ => None |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取所有级别选项
|
|||
/// </summary>
|
|||
/// <returns>级别选项列表</returns>
|
|||
public static List<LevelOption> GetLevelOptions() |
|||
{ |
|||
return new List<LevelOption> |
|||
{ |
|||
new() { Value = Error, Text = "Error" }, |
|||
new() { Value = Warn, Text = "Warning" }, |
|||
new() { Value = Info, Text = "Info" }, |
|||
new() { Value = Debug, Text = "Debug" } |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 级别选项
|
|||
/// </summary>
|
|||
public class LevelOption |
|||
{ |
|||
public int Value { get; set; } |
|||
public string Text { get; set; } = string.Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 时间戳转换为时间字符串
|
|||
/// </summary>
|
|||
/// <param name="timestamp">时间戳(毫秒)</param>
|
|||
/// <param name="full">是否显示完整日期</param>
|
|||
/// <returns>时间字符串</returns>
|
|||
public static string TimestampToTime(long timestamp, bool full = false) |
|||
{ |
|||
// 持续时间 (< 2000-01-01 00:00:00)
|
|||
if (timestamp < 946681200000) |
|||
{ |
|||
return TimestampToDuration(timestamp); |
|||
} |
|||
|
|||
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; |
|||
if (full) |
|||
{ |
|||
return dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); |
|||
} |
|||
return dateTime.ToString("HH:mm:ss.fff"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 时间戳转换为持续时间字符串
|
|||
/// </summary>
|
|||
/// <param name="timestamp">时间戳(毫秒)</param>
|
|||
/// <returns>持续时间字符串</returns>
|
|||
public static string TimestampToDuration(long timestamp) |
|||
{ |
|||
var prefix = timestamp < 0 ? "-" : ""; |
|||
var absTimestamp = Math.Abs(timestamp); |
|||
var timeSpan = TimeSpan.FromMilliseconds(absTimestamp); |
|||
return $"{prefix}{timeSpan.Hours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}.{timeSpan.Milliseconds:D3}"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 时间字符串转换为时间戳
|
|||
/// </summary>
|
|||
/// <param name="timeString">时间字符串</param>
|
|||
/// <returns>时间戳,转换失败返回null</returns>
|
|||
public static long? TimeStringToTimestamp(string timeString) |
|||
{ |
|||
if (string.IsNullOrEmpty(timeString)) |
|||
return null; |
|||
|
|||
// 匹配格式: HH:mm:ss.fff 或 mm:ss.fff 或 ss.fff
|
|||
var match = Regex.Match(timeString, @"^(((\d+):)?(\d+):)?(\d+)(\.(\d+))?$"); |
|||
if (!match.Success) |
|||
return null; |
|||
|
|||
var hours = int.Parse(match.Groups[3].Success ? match.Groups[3].Value : "0"); |
|||
var minutes = int.Parse(match.Groups[4].Success ? match.Groups[4].Value : "0"); |
|||
var seconds = int.Parse(match.Groups[5].Value); |
|||
var milliseconds = match.Groups[7].Success ? int.Parse(match.Groups[7].Value.PadRight(3, '0')) : 0; |
|||
|
|||
return (hours * 3600000L) + (minutes * 60000L) + (seconds * 1000L) + milliseconds; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取日期偏移量
|
|||
/// </summary>
|
|||
/// <param name="timestamp">时间戳</param>
|
|||
/// <returns>日期偏移量</returns>
|
|||
public static long GetDayOffset(long timestamp) |
|||
{ |
|||
if (timestamp < 946681200000) |
|||
return 0; |
|||
|
|||
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; |
|||
var dayStart = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, DateTimeKind.Utc); |
|||
return new DateTimeOffset(dayStart).ToUnixTimeMilliseconds(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 字符串转ID
|
|||
/// </summary>
|
|||
/// <param name="str">字符串</param>
|
|||
/// <returns>ID值</returns>
|
|||
public static int StringToId(string str) |
|||
{ |
|||
if (string.IsNullOrEmpty(str)) |
|||
return 0; |
|||
|
|||
var hash = 5381; |
|||
foreach (var c in str) |
|||
{ |
|||
hash = ((hash << 5) + hash) ^ c; |
|||
} |
|||
return hash; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建正则表达式
|
|||
/// </summary>
|
|||
/// <param name="pattern">模式</param>
|
|||
/// <param name="ignoreCase">是否忽略大小写</param>
|
|||
/// <returns>正则表达式,创建失败返回null</returns>
|
|||
public static Regex? CreateRegex(string pattern, bool ignoreCase = true) |
|||
{ |
|||
if (string.IsNullOrEmpty(pattern)) |
|||
return null; |
|||
|
|||
try |
|||
{ |
|||
var options = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None; |
|||
|
|||
// 检查是否是正则表达式格式 /pattern/flags
|
|||
var regexMatch = Regex.Match(pattern, @"^/(.+)/([gi]*)$"); |
|||
if (regexMatch.Success) |
|||
{ |
|||
var regexPattern = regexMatch.Groups[1].Value; |
|||
var flags = regexMatch.Groups[2].Value; |
|||
|
|||
if (flags.Contains('i')) |
|||
options |= RegexOptions.IgnoreCase; |
|||
if (flags.Contains('g')) |
|||
options |= RegexOptions.Multiline; |
|||
|
|||
return new Regex(regexPattern, options); |
|||
} |
|||
|
|||
// 普通文本搜索,添加转义
|
|||
var escapedPattern = Regex.Escape(pattern); |
|||
return new Regex(escapedPattern, options); |
|||
} |
|||
catch |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 高亮搜索文本
|
|||
/// </summary>
|
|||
/// <param name="text">原文本</param>
|
|||
/// <param name="searchPattern">搜索模式</param>
|
|||
/// <returns>高亮后的文本</returns>
|
|||
public static string HighlightSearchText(string text, string searchPattern) |
|||
{ |
|||
if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(searchPattern)) |
|||
return text; |
|||
|
|||
var regex = CreateRegex(searchPattern); |
|||
if (regex == null) |
|||
return text; |
|||
|
|||
return regex.Replace(text, "<highlight>$&</highlight>"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 格式化文件大小
|
|||
/// </summary>
|
|||
/// <param name="bytes">字节数</param>
|
|||
/// <returns>格式化后的字符串</returns>
|
|||
public static string FormatFileSize(long bytes) |
|||
{ |
|||
string[] sizes = { "B", "KB", "MB", "GB", "TB" }; |
|||
double len = bytes; |
|||
int order = 0; |
|||
while (len >= 1024 && order < sizes.Length - 1) |
|||
{ |
|||
order++; |
|||
len = len / 1024; |
|||
} |
|||
return $"{len:0.##} {sizes[order]}"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析大小字符串
|
|||
/// </summary>
|
|||
/// <param name="sizeString">大小字符串</param>
|
|||
/// <returns>字节数</returns>
|
|||
public static long ParseSizeString(string sizeString) |
|||
{ |
|||
if (string.IsNullOrEmpty(sizeString)) |
|||
return 0; |
|||
|
|||
var match = Regex.Match(sizeString, @"^(\d+)([KMG])?$", RegexOptions.IgnoreCase); |
|||
if (!match.Success) |
|||
return 0; |
|||
|
|||
var size = long.Parse(match.Groups[1].Value); |
|||
var unit = match.Groups[2].Value.ToUpper(); |
|||
|
|||
return unit switch |
|||
{ |
|||
"K" => size * 1024, |
|||
"M" => size * 1024 * 1024, |
|||
"G" => size * 1024 * 1024 * 1024, |
|||
_ => size |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取颜色亮度
|
|||
/// </summary>
|
|||
/// <param name="color">颜色代码</param>
|
|||
/// <returns>亮度值 (0-1)</returns>
|
|||
public static double GetColorBrightness(string color) |
|||
{ |
|||
var match = Regex.Match(color, @"#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})"); |
|||
if (!match.Success) |
|||
return 1; |
|||
|
|||
var r = int.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
var g = int.Parse(match.Groups[2].Value, System.Globalization.NumberStyles.HexNumber); |
|||
var b = int.Parse(match.Groups[3].Value, System.Globalization.NumberStyles.HexNumber); |
|||
|
|||
return (r * r * 0.299 + g * g * 0.587 + b * b * 0.114) / (255.0 * 255.0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取文本颜色
|
|||
/// </summary>
|
|||
/// <param name="backgroundColor">背景颜色</param>
|
|||
/// <returns>文本颜色</returns>
|
|||
public static string GetTextColor(string backgroundColor) |
|||
{ |
|||
var brightness = GetColorBrightness(backgroundColor); |
|||
return brightness >= 0.15 ? "black" : "white"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 生成唯一ID
|
|||
/// </summary>
|
|||
/// <returns>唯一ID</returns>
|
|||
public static string GenerateUniqueId() |
|||
{ |
|||
return Guid.NewGuid().ToString("N"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 安全地执行操作
|
|||
/// </summary>
|
|||
/// <param name="action">要执行的操作</param>
|
|||
/// <param name="defaultValue">默认值</param>
|
|||
/// <returns>操作结果或默认值</returns>
|
|||
public static T SafeExecute<T>(Func<T> action, T defaultValue) |
|||
{ |
|||
try |
|||
{ |
|||
return action(); |
|||
} |
|||
catch |
|||
{ |
|||
return defaultValue; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,638 +0,0 @@ |
|||
using LTEMvcApp.Models; |
|||
using Newtonsoft.Json; |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace LTEMvcApp.Services; |
|||
|
|||
/// <summary>
|
|||
/// LTE日志解析服务 - 实现JavaScript _logListParse方法的核心功能
|
|||
/// </summary>
|
|||
public class LogParserService |
|||
{ |
|||
#region 常量定义
|
|||
|
|||
/// <summary>
|
|||
/// HFN回绕阈值
|
|||
/// </summary>
|
|||
private const int HFN_WRAP_THRESHOLD = 512; |
|||
|
|||
/// <summary>
|
|||
/// 最大日志数量
|
|||
/// </summary>
|
|||
private const int LOGS_MAX = 2000000; |
|||
|
|||
#endregion
|
|||
|
|||
#region 正则表达式
|
|||
|
|||
/// <summary>
|
|||
/// PHY层日志正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpPhy = new(@"^([a-f0-9\-]+)\s+([a-f0-9\-]+)\s+([\d\.\-]+) (\w+): (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 信息1正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpInfo1 = new(@"^([\w\-]+): (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 信息2正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpInfo2 = new(@"^([\w]+) (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// IP日志正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpIP = new(@"^(len=\d+)\s+(\S+)\s+(.*)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// IPsec日志正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpIPsec = new(@"^len=(\d+)\s+(.*)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// SDU长度正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpSDULen = new(@"SDU_len=(\d+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// SIP日志正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpSIP = new(@"^([:\.\[\]\da-f]+)\s+(\S+) (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 媒体请求正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpMediaReq = new(@"^(\S+) (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 信号记录正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpSignalRecord = new(@"Link:\s([\w\d]+)@(\d+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 小区ID正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpCellID = new(@"^([a-f0-9\-]+) (.+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// RRC UE ID正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpRRC_UE_ID = new(@"Changing UE_ID to 0x(\d+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// RRC TMSI正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpRRC_TMSI = new(@"(5G|m)-TMSI '([\dA-F]+)'H", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// RRC新ID正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpRRC_NEW_ID = new(@"newUE-Identity (['\dA-FH]+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// RRC CRNTI正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpRRC_CRNTI = new(@"c-RNTI '([\dA-F]+)'H", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// NAS TMSI正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpNAS_TMSI = new(@"m-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// NAS 5G TMSI正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpNAS_5GTMSI = new(@"5G-TMSI = 0x([\da-f]+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// RRC频段组合正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpRRC_BC = new(@"(EUTRA|MRDC|NR|NRDC) band combinations", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// PDCCH正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpPDCCH = new(@"^\s*(.+)=(\d+)$", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// S1/NGAP正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpS1NGAP = new(@"^([\da-f\-]+)\s+([\da-f\-]+) (([^\s]+) .+)", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// 十六进制转储正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex RegExpHexDump = new(@"^[\da-f]+:(\s+[\da-f]{2}){1,16}\s+.{1,16}$", RegexOptions.Compiled); |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有字段
|
|||
|
|||
/// <summary>
|
|||
/// 字符串到ID的映射缓存
|
|||
/// </summary>
|
|||
private readonly Dictionary<string, int> _stringToIdCache = new(); |
|||
|
|||
/// <summary>
|
|||
/// ID到字符串的映射缓存
|
|||
/// </summary>
|
|||
private readonly Dictionary<int, string> _idToStringCache = new(); |
|||
|
|||
/// <summary>
|
|||
/// 字符串ID计数器
|
|||
/// </summary>
|
|||
private int _stringIdCounter = 0; |
|||
|
|||
#endregion
|
|||
|
|||
#region 公共方法
|
|||
|
|||
/// <summary>
|
|||
/// 解析日志列表 - 对应JavaScript的_logListParse方法
|
|||
/// </summary>
|
|||
/// <param name="client">LTE客户端</param>
|
|||
/// <param name="logs">日志列表</param>
|
|||
/// <param name="isWebSocket">是否为WebSocket消息</param>
|
|||
public void ParseLogList(LTEClient client, List<LTELog> logs, bool isWebSocket = false) |
|||
{ |
|||
var length = logs.Count; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
var log = logs[i]; |
|||
log.Message = log.Message + ""; |
|||
|
|||
client.AddLog(log); |
|||
|
|||
// 解析消息
|
|||
switch (log.Layer) |
|||
{ |
|||
case "PHY": |
|||
if (!ParsePhyLog(client, log, log.Message, isWebSocket)) continue; |
|||
ProcessPhyLog(client, log); |
|||
break; |
|||
|
|||
case "RRC": |
|||
ParseCellId(client, log, isWebSocket); |
|||
var rrcUeIdMatch = RegExpRRC_UE_ID.Match(log.Message); |
|||
if (rrcUeIdMatch.Success) |
|||
{ |
|||
SetSameUe(client, log, int.Parse(rrcUeIdMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber)); |
|||
continue; |
|||
} |
|||
|
|||
var infoMatch = RegExpInfo1.Match(log.Message); |
|||
if (infoMatch.Success) |
|||
{ |
|||
if (!SetLogInfo(client, log, infoMatch.Groups[1].Value)) continue; |
|||
log.Message = infoMatch.Groups[2].Value; |
|||
ProcessRrcLog(client, log); |
|||
} |
|||
|
|||
var bcMatch = RegExpRRC_BC.Match(log.Message); |
|||
if (bcMatch.Success) |
|||
{ |
|||
try |
|||
{ |
|||
var data = log.GetDataString(); |
|||
var jsonData = JsonConvert.DeserializeObject<object>(data); |
|||
// 处理频段组合信息
|
|||
} |
|||
catch |
|||
{ |
|||
// 忽略JSON解析错误
|
|||
} |
|||
} |
|||
break; |
|||
|
|||
case "NAS": |
|||
ParseCellId(client, log, isWebSocket); |
|||
var nasTmsiMatch = RegExpNAS_TMSI.Match(log.Message); |
|||
if (nasTmsiMatch.Success) |
|||
{ |
|||
SetTmsi(client, log, nasTmsiMatch.Groups[1].Value); |
|||
continue; |
|||
} |
|||
|
|||
var nas5gTmsiMatch = RegExpNAS_5GTMSI.Match(log.Message); |
|||
if (nas5gTmsiMatch.Success) |
|||
{ |
|||
SetTmsi(client, log, nas5gTmsiMatch.Groups[1].Value); |
|||
continue; |
|||
} |
|||
|
|||
var nasInfoMatch = RegExpInfo2.Match(log.Message); |
|||
if (nasInfoMatch.Success) |
|||
{ |
|||
if (!SetLogInfo(client, log, nasInfoMatch.Groups[1].Value)) continue; |
|||
log.Message = nasInfoMatch.Groups[2].Value; |
|||
ProcessNasLog(client, log); |
|||
} |
|||
break; |
|||
|
|||
case "MAC": |
|||
ParseCellId(client, log, isWebSocket); |
|||
ParseMacLog(client, log); |
|||
break; |
|||
|
|||
case "IP": |
|||
var ipMatch = RegExpIP.Match(log.Message); |
|||
if (ipMatch.Success) |
|||
{ |
|||
var lenPart = ipMatch.Groups[1].Value; |
|||
log.IpLen = int.Parse(lenPart.Split('=')[1]); |
|||
if (!SetLogInfo(client, log, ipMatch.Groups[2].Value)) continue; |
|||
log.Message = ipMatch.Groups[3].Value; |
|||
client.HasData = true; |
|||
} |
|||
break; |
|||
|
|||
case "GTPU": |
|||
var sduLenMatch = RegExpSDULen.Match(log.Message); |
|||
if (sduLenMatch.Success) |
|||
{ |
|||
log.SduLen = int.Parse(sduLenMatch.Groups[1].Value); |
|||
client.HasData = true; |
|||
} |
|||
break; |
|||
|
|||
case "S1AP": |
|||
case "NGAP": |
|||
var s1ngMatch = RegExpS1NGAP.Match(log.Message); |
|||
if (s1ngMatch.Success) |
|||
{ |
|||
log.Message = s1ngMatch.Groups[3].Value; |
|||
var coreId = int.TryParse(s1ngMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber, null, out var core) ? core : (int?)null; |
|||
var ranId = int.TryParse(s1ngMatch.Groups[2].Value, System.Globalization.NumberStyles.HexNumber, null, out var ran) ? ran : (int?)null; |
|||
|
|||
log.LinkIds = new LinkIds { Core = coreId, Ran = ranId }; |
|||
} |
|||
break; |
|||
|
|||
case "SIP": |
|||
var sipMatch = RegExpSIP.Match(log.Message); |
|||
if (sipMatch.Success) |
|||
{ |
|||
if (!SetLogInfo(client, log, sipMatch.Groups[2].Value)) continue; |
|||
log.Message = sipMatch.Groups[3].Value; |
|||
} |
|||
break; |
|||
|
|||
case "MEDIA": |
|||
var mediaMatch = RegExpMediaReq.Match(log.Message); |
|||
if (mediaMatch.Success) |
|||
{ |
|||
if (!SetLogInfo(client, log, mediaMatch.Groups[1].Value)) continue; |
|||
log.Message = mediaMatch.Groups[2].Value; |
|||
} |
|||
break; |
|||
|
|||
case "IPsec": |
|||
var ipsecMatch = RegExpIPsec.Match(log.Message); |
|||
if (ipsecMatch.Success) |
|||
{ |
|||
log.IpLen = int.Parse(ipsecMatch.Groups[1].Value); |
|||
log.Message = ipsecMatch.Groups[2].Value; |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
break; |
|||
} |
|||
|
|||
// 处理提示信息
|
|||
ProcessHints(client, log); |
|||
} |
|||
|
|||
// 限制日志数量
|
|||
if (client.LogCount > LOGS_MAX) |
|||
{ |
|||
var excess = client.LogCount - LOGS_MAX; |
|||
client.Logs.RemoveRange(0, excess); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 私有方法
|
|||
|
|||
/// <summary>
|
|||
/// 解析PHY日志
|
|||
/// </summary>
|
|||
private bool ParsePhyLog(LTEClient client, LTELog log, string message, bool isWebSocket) |
|||
{ |
|||
if (!isWebSocket) |
|||
{ |
|||
var phyMatch = RegExpPhy.Match(message); |
|||
if (!phyMatch.Success) |
|||
{ |
|||
Console.WriteLine($"Bad PHY log: {log}"); |
|||
return false; |
|||
} |
|||
|
|||
log.Cell = int.Parse(phyMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
log.Rnti = int.Parse(phyMatch.Groups[2].Value, System.Globalization.NumberStyles.HexNumber); |
|||
log.Channel = phyMatch.Groups[4].Value; |
|||
log.Message = phyMatch.Groups[5].Value; |
|||
} |
|||
|
|||
client.HasPhy = true; |
|||
client.HasCell = true; |
|||
client.HasRnti = true; |
|||
|
|||
// 解析PHY参数
|
|||
var lines = log.GetData(); |
|||
foreach (var line in lines) |
|||
{ |
|||
var parts = line.Split('='); |
|||
if (parts.Length == 2) |
|||
{ |
|||
ParsePhyParameter(client, log, parts[0].Trim(), parts[1].Trim()); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析PHY参数
|
|||
/// </summary>
|
|||
private void ParsePhyParameter(LTEClient client, LTELog log, string param, string value) |
|||
{ |
|||
switch (param.ToLower()) |
|||
{ |
|||
case "frame": |
|||
if (int.TryParse(value, out var frame)) |
|||
{ |
|||
log.Frame = frame; |
|||
client.HasPhy = true; |
|||
} |
|||
break; |
|||
case "subframe": |
|||
if (int.TryParse(value, out var subframe)) |
|||
{ |
|||
log.Subframe = subframe; |
|||
} |
|||
break; |
|||
case "slot": |
|||
if (int.TryParse(value, out var slot)) |
|||
{ |
|||
log.Slot = slot; |
|||
} |
|||
break; |
|||
case "symbol": |
|||
if (int.TryParse(value, out var symbol)) |
|||
{ |
|||
log.Symbol = symbol; |
|||
} |
|||
break; |
|||
case "ant": |
|||
if (int.TryParse(value, out var ant)) |
|||
{ |
|||
log.AntennaPort = ant; |
|||
} |
|||
break; |
|||
case "rb_start": |
|||
if (int.TryParse(value, out var rbStart)) |
|||
{ |
|||
log.RbStart = rbStart; |
|||
client.HasRb = true; |
|||
} |
|||
break; |
|||
case "rb_count": |
|||
if (int.TryParse(value, out var rbCount)) |
|||
{ |
|||
log.RbCount = rbCount; |
|||
client.HasRb = true; |
|||
} |
|||
break; |
|||
case "mcs": |
|||
if (int.TryParse(value, out var mcs)) |
|||
{ |
|||
log.Mcs = mcs; |
|||
} |
|||
break; |
|||
case "tbs": |
|||
if (int.TryParse(value, out var tbs)) |
|||
{ |
|||
log.Tbs = tbs; |
|||
} |
|||
break; |
|||
case "harq_id": |
|||
if (int.TryParse(value, out var harqId)) |
|||
{ |
|||
log.HarqId = harqId; |
|||
} |
|||
break; |
|||
case "harq_ndi": |
|||
if (bool.TryParse(value, out var harqNdi)) |
|||
{ |
|||
log.HarqNdi = harqNdi; |
|||
} |
|||
break; |
|||
case "harq_rv": |
|||
if (int.TryParse(value, out var harqRv)) |
|||
{ |
|||
log.HarqRedundancyVersion = harqRv; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理PHY日志
|
|||
/// </summary>
|
|||
private void ProcessPhyLog(LTEClient client, LTELog log) |
|||
{ |
|||
// 处理HARQ信息
|
|||
if (log.HarqId.HasValue) |
|||
{ |
|||
ProcessHarqLog(client, log, log.HarqId.Value); |
|||
} |
|||
|
|||
// 处理帧信息
|
|||
if (log.Frame.HasValue) |
|||
{ |
|||
var frame = log.Frame.Value; |
|||
var hfn = frame >> 10; |
|||
var sfn = frame & 0x3FF; |
|||
|
|||
if (client.Frame.Timestamp == -1) |
|||
{ |
|||
client.Frame.Timestamp = log.Timestamp; |
|||
client.Frame.Hfn = hfn; |
|||
client.Frame.Last = sfn; |
|||
} |
|||
else |
|||
{ |
|||
var diff = sfn - client.Frame.Last; |
|||
if (diff < -HFN_WRAP_THRESHOLD) |
|||
{ |
|||
client.Frame.Hfn++; |
|||
} |
|||
else if (diff > HFN_WRAP_THRESHOLD) |
|||
{ |
|||
client.Frame.Hfn--; |
|||
} |
|||
|
|||
client.Frame.Last = sfn; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理HARQ日志
|
|||
/// </summary>
|
|||
private void ProcessHarqLog(LTEClient client, LTELog log, int harq) |
|||
{ |
|||
if (log.Cell.HasValue && log.UeId.HasValue) |
|||
{ |
|||
var cell = log.Cell.Value; |
|||
var ueId = log.UeId.Value; |
|||
var key = ueId * 32 + harq; |
|||
|
|||
if (!client.LastHarq.ContainsKey(cell)) |
|||
{ |
|||
client.LastHarq[cell] = new Dictionary<int, LTELog>(); |
|||
} |
|||
|
|||
client.LastHarq[cell][key] = log; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析小区ID
|
|||
/// </summary>
|
|||
private void ParseCellId(LTEClient client, LTELog log, bool isWebSocket) |
|||
{ |
|||
if (!isWebSocket) |
|||
{ |
|||
var cellMatch = RegExpCellID.Match(log.Message); |
|||
if (cellMatch.Success) |
|||
{ |
|||
log.Cell = int.Parse(cellMatch.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
log.Message = cellMatch.Groups[2].Value; |
|||
client.HasCell = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置日志信息
|
|||
/// </summary>
|
|||
private bool SetLogInfo(LTEClient client, LTELog log, string info) |
|||
{ |
|||
if (string.IsNullOrEmpty(info) || info == "?") |
|||
return false; |
|||
|
|||
log.Info = StringToId(info); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 字符串转ID
|
|||
/// </summary>
|
|||
private int StringToId(string str) |
|||
{ |
|||
if (_stringToIdCache.TryGetValue(str, out var id)) |
|||
{ |
|||
return id; |
|||
} |
|||
|
|||
id = ++_stringIdCounter; |
|||
_stringToIdCache[str] = id; |
|||
_idToStringCache[id] = str; |
|||
return id; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// ID转字符串
|
|||
/// </summary>
|
|||
private string IdToString(int id) |
|||
{ |
|||
return _idToStringCache.TryGetValue(id, out var str) ? str : ""; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置相同UE
|
|||
/// </summary>
|
|||
private void SetSameUe(LTEClient client, LTELog log, int ueId) |
|||
{ |
|||
log.UeId = ueId; |
|||
if (!client.UeList.ContainsKey(ueId)) |
|||
{ |
|||
client.UeList[ueId] = new UEInfo { UeId = ueId }; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理RRC日志
|
|||
/// </summary>
|
|||
private void ProcessRrcLog(LTEClient client, LTELog log) |
|||
{ |
|||
// 处理RRC TMSI
|
|||
var tmsiMatch = RegExpRRC_TMSI.Match(log.Message); |
|||
if (tmsiMatch.Success) |
|||
{ |
|||
SetTmsi(client, log, tmsiMatch.Groups[2].Value); |
|||
} |
|||
|
|||
// 处理RRC CRNTI
|
|||
var crntiMatch = RegExpRRC_CRNTI.Match(log.Message); |
|||
if (crntiMatch.Success) |
|||
{ |
|||
SetRnti(client, log, crntiMatch.Groups[1].Value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理NAS日志
|
|||
/// </summary>
|
|||
private void ProcessNasLog(LTEClient client, LTELog log) |
|||
{ |
|||
// NAS日志处理逻辑
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析MAC日志
|
|||
/// </summary>
|
|||
private void ParseMacLog(LTEClient client, LTELog log) |
|||
{ |
|||
// MAC日志解析逻辑
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理提示信息
|
|||
/// </summary>
|
|||
private void ProcessHints(LTEClient client, LTELog log, Dictionary<string, object>? config = null) |
|||
{ |
|||
// 处理提示信息逻辑
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置TMSI
|
|||
/// </summary>
|
|||
private void SetTmsi(LTEClient client, LTELog log, string tmsi) |
|||
{ |
|||
var tmsiId = int.Parse(tmsi, System.Globalization.NumberStyles.HexNumber); |
|||
if (log.UeId.HasValue) |
|||
{ |
|||
client.TmsiToUeId[tmsiId] = log.UeId.Value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置RNTI
|
|||
/// </summary>
|
|||
private void SetRnti(LTEClient client, LTELog log, string rnti) |
|||
{ |
|||
var rntiId = int.Parse(rnti, System.Globalization.NumberStyles.HexNumber); |
|||
if (log.UeId.HasValue) |
|||
{ |
|||
client.RntiToUeId[rntiId] = log.UeId.Value; |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
Loading…
Reference in new issue