Browse Source

修改 log 文件

feature/LteClientLogFun
hyh 1 month ago
parent
commit
7d793209bd
  1. 44
      LTEMvcApp/Models/LTEClient.cs
  2. 121
      LTEMvcApp/Models/LogChannelConfig.cs
  3. 254
      LTEMvcApp/Models/LogConstants.cs
  4. 74
      LTEMvcApp/Models/LogDirection.cs
  5. 229
      LTEMvcApp/Models/LogEventManager.cs
  6. 270
      LTEMvcApp/Models/LogFilterConfig.cs
  7. 121
      LTEMvcApp/Models/LogModelConfig.cs
  8. 314
      LTEMvcApp/Models/LogState.cs
  9. 331
      LTEMvcApp/Models/LogUtils.cs
  10. 895
      LTEMvcApp/Models/LogsManager.cs
  11. 112
      LTEMvcApp/README_LTEClient_Refactoring.md

44
LTEMvcApp/Models/LTEClient.cs

@ -287,32 +287,28 @@ public class LTEClient
#endregion
#region 解析器私有字段
#region 依赖注入
/// <summary>
/// 字符串到ID的映射缓存
/// 日志管理器
/// </summary>
private readonly Dictionary<string, int> _stringToIdCache = new();
private readonly LogsManager _logsManager;
/// <summary>
/// ID到字符串的映射缓存
/// 获取日志管理器实例
/// </summary>
private readonly Dictionary<int, string> _idToStringCache = new();
/// <summary>
/// 字符串ID计数器
/// </summary>
private int _stringIdCounter = 0;
public LogsManager LogsManager => _logsManager;
#endregion
#region 构造函数
public LTEClient(ClientConfig config)
public LTEClient(ClientConfig config, LogsManager logsManager)
{
Config = config;
Name = config.Name;
ClientId = GenerateClientId();
_logsManager = logsManager;
ResetParserState();
}
@ -320,11 +316,13 @@ public class LTEClient
/// 使用名称创建客户端
/// </summary>
/// <param name="name">客户端名称</param>
public LTEClient(string name)
/// <param name="logsManager">日志管理器</param>
public LTEClient(string name, LogsManager logsManager)
{
Config = new ClientConfig { Name = name, Enabled = true };
Name = name;
ClientId = GenerateClientId();
_logsManager = logsManager;
ResetParserState();
}
@ -436,27 +434,19 @@ public class LTEClient
}
/// <summary>
/// 字符串转ID
/// 字符串转ID - 委托给LogsManager
/// </summary>
public int StringToId(string str)
{
if (_stringToIdCache.TryGetValue(str, out var id))
{
return id;
}
id = ++_stringIdCounter;
_stringToIdCache[str] = id;
_idToStringCache[id] = str;
return id;
return _logsManager.String2Id(str);
}
/// <summary>
/// ID转字符串
/// ID转字符串 - 委托给LogsManager
/// </summary>
public string IdToString(int id)
{
return _idToStringCache.TryGetValue(id, out var str) ? str : "";
return _logsManager.Id2String(id);
}
#endregion
@ -969,10 +959,8 @@ public class LTEClient
TimestampOffset = 0;
LastCell = null;
// 重置解析器缓存
_stringToIdCache.Clear();
_idToStringCache.Clear();
_stringIdCounter = 0;
// 注意:LogsManager 的字符串ID缓存是全局的,不需要重置
// 这样可以保持字符串ID的一致性
}
/// <summary>

121
LTEMvcApp/Models/LogChannelConfig.cs

@ -1,121 +0,0 @@
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; }
}
}

254
LTEMvcApp/Models/LogConstants.cs

@ -1,254 +0,0 @@
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
}
}

74
LTEMvcApp/Models/LogDirection.cs

@ -1,74 +0,0 @@
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;
}
}

229
LTEMvcApp/Models/LogEventManager.cs

@ -1,229 +0,0 @@
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
};
}
}
}

270
LTEMvcApp/Models/LogFilterConfig.cs

@ -1,270 +0,0 @@
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
};
}
}
}

121
LTEMvcApp/Models/LogModelConfig.cs

@ -1,121 +0,0 @@
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; }
}
}

314
LTEMvcApp/Models/LogState.cs

@ -1,314 +0,0 @@
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}";
}
}
}

331
LTEMvcApp/Models/LogUtils.cs

@ -1,331 +0,0 @@
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;
}
}
}
}

895
LTEMvcApp/Models/LogsManager.cs

@ -0,0 +1,895 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LTEMvcApp.Models
{
/// <summary>
/// 日志管理器 - 从JavaScript logs.js迁移
/// </summary>
public class LogsManager
{
#region 常量定义
public const int DIR_NONE = 0;
public const int DIR_UL = 1;
public const int DIR_DL = 2;
public const int DIR_FROM = 3;
public const int DIR_TO = 4;
#endregion
#region 静态字段
private static readonly string[] modelList = { "RUE", "UE", "PROBE", "ENB", "N3IWF", "MBMSGW", "MME", "IMS", "LICENSE", "MONITOR" };
private static readonly Dictionary<string, object> modelConfig = new()
{
{ "RUE", new { icon = "icon-ue2" } },
{ "UE", new { icon = "icon-ue" } },
{ "PROBE", new { } },
{ "ENB", new { icon = "icon-air", modelHint = "enb|gnb|bbu", title = "RAN" } },
{ "N3IWF", new { icon = "icon-air", modelHint = "n3iwf", title = "N3IWF" } },
{ "MME", new { icon = "icon-server", modelHint = "epc|mme|amf", title = "CN" } },
{ "MBMSGW", new { icon = "icon-download", modelHint = "mbms" } },
{ "IMS", new { icon = "icon-dial" } },
{ "MONITOR", new { icon = "icon-monitor" } },
{ "LICENSE", new { icon = "icon-file" } }
};
private static readonly Dictionary<string, object> layerConfig = new()
{
{ "PHY", new { color = "#606070", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } }, debug = new { level = "debug", max_size = 1 } } },
{ "MAC", new { color = "#10A0FF", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } }, debug = new { level = "debug", max_size = 1 } } },
{ "RLC", new { color = "#FFFF80", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } } } },
{ "PDCP", new { color = "#B0D0B0", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } } } },
{ "RRC", new { color = "#00FF60", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 1 } }, debug = new { level = "debug", max_size = 1 } } },
{ "NAS", new { color = "#90FFC0", dir = new Dictionary<string, int> { { "UE", 2 }, { "PROBE", 3 }, { "ENB", 3 }, { "N3IWF", 3 }, { "MME", 1 } }, debug = new { level = "debug", max_size = 1 } } },
{ "GTPU", new { color = "#FF80FF", dir = new Dictionary<string, int> { { "ENB", 2 }, { "N3IWF", 2 }, { "MME", 1 }, { "MBMSGW", 1 }, { "UE", 1 }, { "RUE", 2 } }, epc = true, max = new { max_size = 32 } } },
{ "GTPC", new { color = "#FFC0FF", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "IP", new { color = "#E0E0E0", dir = new Dictionary<string, int> { { "RUE", 2 }, { "UE", 2 }, { "N3IWF", 3 }, { "PROBE", 3 }, { "MME", 3 } }, max = new { max_size = 32 } } },
{ "S1AP", new { color = "#80FF00", dir = new Dictionary<string, int> { { "ENB", 2 }, { "MME", 1 } }, epc = true, debug = new { level = "debug", max_size = 1 } } },
{ "NGAP", new { color = "#5DD122", dir = new Dictionary<string, int> { { "ENB", 2 }, { "MME", 1 }, { "N3IWF", 2 } }, epc = true, debug = new { level = "debug", max_size = 1 } } },
{ "X2AP", new { color = "#FF8000", dir = new Dictionary<string, int> { { "ENB", 2 } } } },
{ "XnAP", new { color = "#FFB020", dir = new Dictionary<string, int> { { "ENB", 2 } } } },
{ "M2AP", new { color = "#7F675B", dir = new Dictionary<string, int> { { "ENB", 2 }, { "MBMSGW", 1 } }, epc = true } },
{ "IMS", new { color = "#C0FF80", dir = new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } }, debug = new { level = "debug", max_size = 1 }, epc = true } },
{ "CX", new { color = "#F49542", dir = new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } }, epc = true } },
{ "RX", new { color = "#D4A190", dir = new Dictionary<string, int> { { "MME", 2 }, { "IMS", 1 } }, epc = true } },
{ "COM", new { color = "#C000FF", dir = new Dictionary<string, int> { { "*", 2 } } } },
{ "SIP", new { color = "#C080FF", dir = new Dictionary<string, int> { { "IMS", 1 } }, epc = true, debug = new { level = "debug", max_size = 1 } } },
{ "MEDIA", new { color = "#800040", dir = new Dictionary<string, int> { { "IMS", 1 } }, epc = true } },
{ "RTP", new { color = "#FF00C0", dir = new Dictionary<string, int> { { "IMS", 1 } }, epc = true } },
{ "PROD", new { color = "#80C0FF", dir = new Dictionary<string, int> { } } },
{ "S6", new { color = "#F44B42", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "S13", new { color = "#D6F953", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "SGsAP", new { color = "#FF7DB8", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "SBcAP", new { color = "#8FA00F", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "N8", new { color = "#106020", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "N12", new { color = "#602020", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "N13", new { color = "#202060", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "N17", new { color = "#D6F953", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "N50", new { color = "#8FA00F", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "MMS", new { color = "#B4D98F", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "HTTP2", new { color = "#644824", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "LCSAP", new { color = "#cfd50d", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "LPPa", new { color = "#0dcfd5", dir = new Dictionary<string, int> { { "ENB", 2 }, { "MME", 3 } }, epc = true } },
{ "NL1", new { color = "#d040cf", dir = new Dictionary<string, int> { { "MME", 2 } }, epc = true } },
{ "NRPPa", new { color = "#0dd5cf", dir = new Dictionary<string, int> { { "ENB", 2 }, { "MME", 3 } }, epc = true } },
{ "IKEV2", new { color = "#C0B732", dir = new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 }, { "N3IWF", 1 } } } },
{ "SWU", new { color = "#101080", dir = new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 } } } },
{ "NWU", new { color = "#2080B8", dir = new Dictionary<string, int> { { "UE", 2 }, { "MME", 1 } } } },
{ "IPSEC", new { color = "#F04010", dir = new Dictionary<string, int> { { "UE", 2 }, { "IMS", 1 }, { "N3IWF", 1 }, { "MME", 1 } }, epc = true, max = new { max_size = 32 } } },
{ "N3IWF", new { color = "#C080C0", dir = new Dictionary<string, int> { { "UE", 2 }, { "N3IWF", 1 } } } },
{ "TRX", new { color = "#42C0a0", dir = new Dictionary<string, int> { { "UE", 2 }, { "ENB", 1 } }, debug = new { level = "debug" } } },
{ "MON", new { color = "#C0C080", dir = new Dictionary<string, int> { } } },
{ "EVENT", new { color = "#80C0FF", dir = new Dictionary<string, int> { } } },
{ "ALARM", new { color = "#FF8040", dir = new Dictionary<string, int> { } } },
{ "LIC", new { color = "#ff80c0", dir = new Dictionary<string, int> { { "LICENSE", 1 } } } },
{ "OTS", new { color = "#8080FF", dir = new Dictionary<string, int> { } } },
{ "ERROR", new { color = "#ff0000", dir = new Dictionary<string, int> { } } }
};
#endregion
#region 实例字段
private readonly Dictionary<string, object> _logs = new();
private readonly int _logDefaultLevel = 0; // localStorage.debug >> 0
private readonly string[] _logsMethods = { "error", "warn", "info", "debug", "prof" };
private readonly long _startDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private readonly Dictionary<string, object> _stringIDs = new();
private readonly List<string> _stringList = new();
private readonly Dictionary<int, object> _eventListeners = new();
private int _eventListenerId = 0;
#endregion
#region 属性
public string Mode { get; set; } = "web";
public string FontDef { get; set; } = "13px helvetica, arial, verdana, sans-serif";
public int LastClientId { get; set; } = 0;
public int LogsMax { get; set; } = 100000;
public List<int[]> LogsRange { get; set; } = new();
public List<int> LogsInfo { get; set; } = new();
public List<string> LogsLayers { get; set; } = new();
public string ExportTimeFormat { get; set; } = "HH:mm:ss.fff";
#endregion
#region 构造函数
public LogsManager()
{
Init();
}
#endregion
#region 初始化方法
public void Init()
{
// 初始化通道定义
var def = new Dictionary<string, object>();
var ids = new Dictionary<string, int>();
// 这里可以添加通道定义的初始化逻辑
// 对应JavaScript中的_channelsDef.forEach逻辑
}
#endregion
#region 日志相关方法
public void SetLogger(string name, object instance)
{
var module = new { level = _logDefaultLevel, list = new List<object>() };
_logs[name] = module;
// 为每个日志方法创建对应的函数
for (int i = 0; i < _logsMethods.Length; i++)
{
var methodName = _logsMethods[i];
var level = 1 << i;
// 这里需要动态设置instance的方法
}
}
private void Log(string module, string id, object log)
{
// 实现日志记录逻辑
var logObj = new
{
ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - _startDate,
level = ((dynamic)log).level,
args = ((dynamic)log).args
};
var moduleObj = (dynamic)_logs[module];
moduleObj.list.Add(logObj);
if (moduleObj.list.Count > 20)
moduleObj.list.RemoveAt(0);
if ((logObj.level & moduleObj.level) != 0)
{
if ((logObj.level & 0x1) != 0 && moduleObj.level == 0x1)
{
foreach (var item in moduleObj.list)
{
LogDisplay(id, item);
}
moduleObj.list.Clear();
}
else
{
LogDisplay(id, logObj);
}
}
}
private void LogDisplay(string id, object log)
{
var time = Ts2Time(((dynamic)log).ts);
var level = ((dynamic)log).level;
var args = ((dynamic)log).args;
string style = "";
int index = 0;
if ((level & 0x1) != 0)
{
style = "background: red";
index = 0;
}
else if ((level & 0x2) != 0)
{
style = "background: yellow";
index = 1;
}
else if ((level & 0x10) != 0)
{
style = "background: #00ff00";
index = 4;
}
else
{
index = (int)Math.Floor(Math.Log2(level));
}
// 这里可以输出到控制台或日志系统
Console.WriteLine($"[{time}][{_logsMethods[index].ToUpper()}][{id}]");
}
#endregion
#region 时间转换方法
public string Ts2Time(long ts, bool full = false)
{
// 持续时间 (< 2000-01-01 00:00:00)
if (ts < 946681200000)
return Ts2Duration(ts);
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(ts).DateTime;
var prefix = "";
if (full)
{
var day = dateTime.Day;
var month = dateTime.Month;
var year = dateTime.Year;
prefix = $"{year}-{month:D2}-{day:D2} ";
}
var h = dateTime.Hour;
var m = dateTime.Minute;
var s = dateTime.Second;
var ms = dateTime.Millisecond;
return $"{prefix}{h:D2}:{m:D2}:{s:D2}.{ms:D3}";
}
public string Ts2Duration(long ts)
{
var prefix = "";
if (ts < 0)
{
prefix = "-";
ts = -ts;
}
var timeSpan = TimeSpan.FromMilliseconds(ts);
var h = (int)timeSpan.TotalHours;
var m = timeSpan.Minutes;
var s = timeSpan.Seconds;
var ms = timeSpan.Milliseconds;
return $"{prefix}{h:D2}:{m:D2}:{s:D2}.{ms:D3}";
}
public long TsDayOffset(long ts)
{
if (ts < 946681200000)
return 0;
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(ts).DateTime;
var dayStart = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0);
return new DateTimeOffset(dayStart).ToUnixTimeMilliseconds();
}
public long? Str2Ts(string value)
{
var match = Regex.Match(value, @"^(((\d+):)?(\d+):)?(\d+)(\.(\d+))?$");
if (match.Success)
{
var h = int.Parse(match.Groups[3].Success ? match.Groups[3].Value : "0");
var m = int.Parse(match.Groups[4].Success ? match.Groups[4].Value : "0");
var s = int.Parse(match.Groups[5].Value);
var msStr = match.Groups[7].Success ? match.Groups[7].Value : "0";
var ms = (int)((int.Parse(msStr) * Math.Pow(10, 3 - msStr.Length)));
return h * 3600000L + m * 60000L + s * 1000L + ms;
}
return null;
}
#endregion
#region 字符串处理方法
public int String2Id(string str)
{
if (_stringIDs.ContainsKey(str))
return (int)_stringIDs[str];
var id = _stringList.Count + 1;
_stringIDs[str] = id;
_stringList.Add(str);
return id;
}
public string Id2String(int id)
{
if (id > 0 && id <= _stringList.Count)
return _stringList[id - 1];
return "";
}
public string Tooltip(string txt)
{
return txt?.Replace(" ", "&nbsp;") ?? "";
}
public T Merge<T>(T dst, T src, bool override_ = false) where T : class
{
// 实现对象合并逻辑
return dst;
}
public T Clone<T>(T o) where T : class
{
// 实现对象克隆逻辑
return o;
}
public string PadLeft(string txt, char c, int len)
{
return txt.PadLeft(len, c);
}
#endregion
#region 哈希和工具方法
public uint Hash(string str)
{
uint h = 5381;
for (int i = 0; i < str.Length; i++)
{
uint c = (uint)str[i];
h = ((h << 5) + h) ^ c;
}
return h;
}
public string[] Levels { get; } = { "none", "error", "warn", "info", "debug" };
public int GetLevel(string str)
{
return Array.IndexOf(Levels, str);
}
public string GetHtmlIcon(string c)
{
return $"<img src='data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' class='x-tree-icon {c}'>";
}
public List<T> MapFilter<T>(T[] array, Func<T, int, T> func)
{
var res = new List<T>();
for (int i = 0; i < array.Length; i++)
{
var item = func(array[i], i);
if (item != null)
res.Add(item);
}
return res;
}
public void DrawConstellation(object canvas, byte[] constel)
{
// 实现星座图绘制逻辑
}
public string CreateIcon(string icon, string tooltip)
{
return $"<img src=\"resources/images/{icon}.png\" height=12 title=\"{tooltip}\">";
}
public double GetColLight(string c)
{
var match = Regex.Match(c, @"#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})");
if (match.Success)
{
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);
var h = (r * r * 0.299 + g * g * 0.587 + b * b * 0.114) / (255.0 * 255.0);
return h;
}
return 1;
}
public string GetTextColor(string bgColor)
{
var h = GetColLight(bgColor);
return h >= 0.15 ? "black" : "white";
}
#endregion
#region 数组操作方法
public void ArrayIntersect<T>(List<T> a, List<T> b)
{
for (int i = a.Count - 1; i >= 0; i--)
{
var index = b.IndexOf(a[i]);
if (index < 0)
a.RemoveAt(i);
}
}
public long String2Size(string str)
{
if (string.IsNullOrEmpty(str)) return 0;
var match = Regex.Match(str, @"^(\d+)([KMG])$");
if (match.Success)
{
var size = long.Parse(match.Groups[1].Value);
var unit = match.Groups[2].Value;
return unit switch
{
"K" => size * 1000,
"M" => size * 1000000,
"G" => size * 1000000000,
_ => size
};
}
return long.Parse(str);
}
public Dictionary<string, object> SplitArgs(string args, Dictionary<string, object> cfg)
{
var hash = new Dictionary<string, object>();
var argList = Regex.Split(args, @"\s+");
foreach (var arg in argList)
{
var parts = arg.Split('=');
if (parts.Length == 2)
{
var key = parts[0];
var value = parts[1];
if (cfg.ContainsKey(key))
{
var cfgType = cfg[key].GetType();
if (cfgType == typeof(int))
{
hash[key] = int.Parse(value);
}
else if (cfgType == typeof(long))
{
hash[key] = long.Parse(value);
}
else
{
hash[key] = value;
}
}
else
{
hash[key] = value;
}
}
}
return hash;
}
#endregion
#region 事件处理方法
public int RegisterEventListener(string type, Action<object> cb)
{
var id = _eventListenerId++;
_eventListeners[id] = new { type = type, cb = cb };
return id;
}
public void UnregisterEventListener(int id)
{
_eventListeners.Remove(id);
}
public void SendEvent(object eventObj)
{
var type = ((dynamic)eventObj).type;
foreach (var kvp in _eventListeners)
{
var el = (dynamic)kvp.Value;
if (el.type == type || el.type == "*")
{
el.cb(eventObj);
}
}
}
#endregion
#region 鼠标事件方法
public string GetMouseWheelEvent()
{
// 检测浏览器类型并返回相应的事件名
return "mousewheel"; // 简化实现
}
public int GetMouseWheelDelta(object eventObj)
{
// 实现鼠标滚轮事件处理
return 0;
}
#endregion
#region 文件导出方法
public void ExportFile(object data, string filename, string mimetype)
{
// 实现文件导出逻辑
}
#endregion
#region 日志排序方法
public int SortLogs(LTELog a, LTELog b)
{
var tsCompare = a.Timestamp.CompareTo(b.Timestamp);
if (tsCompare != 0) return tsCompare;
return a.Id.CompareTo(b.Id);
}
public int GetLogIndexByTimestamp(List<LTELog> list, long timestamp)
{
var l = list.Count;
var b = l - 1;
var a = 0;
while (a <= b)
{
var i = (a + b) >> 1;
var ts_i = list[i].Timestamp;
if (timestamp < ts_i)
{
b = i - 1;
}
else if (timestamp > ts_i)
{
a = i + 1;
}
else
{
while (++i < l && list[i].Timestamp == timestamp) ;
return i;
}
}
return a;
}
public List<LTELog> MergeLogs(List<LTELog> srcList, List<LTELog> dstList)
{
var srcLength = srcList.Count;
var t0 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var dstLength = dstList.Count;
if (dstLength > 500000 && srcLength < 50000)
{
// 排序新数据
srcList.Sort(SortLogs);
var t1 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// 插入
var a = GetLogIndexByTimestamp(dstList, srcList[0].Timestamp);
for (int i = 0; i < srcLength; i++)
{
var log = srcList[i];
var ts = log.Timestamp;
while (a < dstLength)
{
var ts_a = dstList[a].Timestamp;
if (ts < ts_a) break;
a++;
}
if (a == dstLength)
{
while (i < srcLength)
{
dstList.Add(srcList[i++]);
}
break;
}
dstList.Insert(a, log);
a++;
dstLength++;
}
}
else
{
dstList.AddRange(srcList);
var t1 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
dstList.Sort(SortLogs);
dstLength = dstList.Count;
}
return dstList;
}
#endregion
#region 鼠标移动注册方法
public void RegisterMouseMove(Action<object> cb)
{
// 实现鼠标移动事件注册
}
#endregion
#region 存储更新方法
public void StoreUpdate<T>(Dictionary<string, T> cache, List<T> store, List<T> list, string id, bool addOnly = false)
{
var found = new Dictionary<string, bool>();
foreach (var item in list)
{
var prop = ((dynamic)item).GetType().GetProperty(id)?.GetValue(item)?.ToString();
if (prop != null)
{
if (!cache.ContainsKey(prop))
{
store.Add(item);
cache[prop] = item;
}
else
{
// 更新现有项
var index = store.IndexOf(cache[prop]);
if (index >= 0)
store[index] = item;
}
found[prop] = true;
}
}
if (!addOnly)
{
for (int i = store.Count - 1; i >= 0; i--)
{
var item = store[i];
var prop = ((dynamic)item).GetType().GetProperty(id)?.GetValue(item)?.ToString();
if (prop != null && !found.ContainsKey(prop))
{
store.RemoveAt(i);
cache.Remove(prop);
}
}
}
}
public T StoreGetById<T>(List<T> store, string id, object value)
{
foreach (var item in store)
{
var itemValue = ((dynamic)item).GetType().GetProperty(id)?.GetValue(item);
if (Equals(itemValue, value))
return item;
}
return default(T);
}
#endregion
#region 日志状态更新方法
public void LogStateUpdate(object logState, Action cb)
{
// 实现日志状态更新逻辑
cb?.Invoke();
}
public void LogStateUpdateEnd(object logState, List<LTELog> logs, Action cb)
{
// 实现日志状态更新结束逻辑
cb?.Invoke();
}
#endregion
#region Canvas设置方法
public void SetCanvasSize(object canvas, int width, int height)
{
// 实现Canvas大小设置逻辑
}
#endregion
#region 文件加载方法
public void LoadFile(object options)
{
// 实现文件加载逻辑
}
#endregion
#region 警告框方法
public void WarnBox(string title, string message, Action cb = null, object scope = null)
{
// 实现警告框显示逻辑
}
#endregion
#region 窗口自动高度设置方法
public void SetWindowAutoHeight(object win, object config)
{
// 实现窗口自动高度设置逻辑
}
#endregion
#region 正则表达式方法
public Regex GetRexExp(string value)
{
var match = Regex.Match(value ?? "", @"^\/(.+)\/([gi]*)$");
try
{
if (match.Success)
{
var pattern = match.Groups[1].Value;
var flags = match.Groups[2].Value;
var options = RegexOptions.None;
if (flags.Contains("i")) options |= RegexOptions.IgnoreCase;
if (flags.Contains("g")) options |= RegexOptions.Multiline;
return new Regex($"({pattern})", options);
}
return new Regex($"({value})", RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
catch
{
return null;
}
}
#endregion
#region 本地存储方法
public T LocalStorageGet<T>(string name)
{
// 实现本地存储获取逻辑
return default(T);
}
public void LocalStorageSet<T>(string name, T value)
{
// 实现本地存储设置逻辑
}
#endregion
#region 测试方法
public void TestColor()
{
// 实现颜色测试逻辑
}
#endregion
#region 工具方法
public string FilterValueFormater(string type, object value, bool highLight = false)
{
// 实现过滤器值格式化逻辑
return value?.ToString() ?? "";
}
public void UpdateFilter()
{
// 实现过滤器更新逻辑
}
public void LockFilter(bool state)
{
// 实现过滤器锁定逻辑
}
public void SelectLog(LTELog log)
{
// 实现日志选择逻辑
}
public void GotoTime(object time)
{
// 实现时间跳转逻辑
}
public void UpdateLogs(object parameters = null)
{
// 实现日志更新逻辑
}
public List<LTELog> GetLogs(object client = null)
{
// 实现获取日志逻辑
return new List<LTELog>();
}
public List<object> GetClients()
{
// 实现获取客户端列表逻辑
return new List<object>();
}
public void AddClient(object cfg, bool persistent = false, bool save = true)
{
// 实现添加客户端逻辑
}
public object GetClientById(string storeId)
{
// 实现根据ID获取客户端逻辑
return null;
}
public bool RemoveClient(object client)
{
// 实现移除客户端逻辑
return false;
}
public void SaveConfig()
{
// 实现配置保存逻辑
}
public void RefreshClientGrid()
{
// 实现客户端网格刷新逻辑
}
public void Load(bool loading)
{
// 实现加载状态设置逻辑
}
public void GridColumnsUpdate()
{
// 实现网格列更新逻辑
}
public void ContextMenu(List<object> items, LTELog log)
{
// 实现上下文菜单逻辑
}
public void AddTab(object tab, bool active = false)
{
// 实现添加标签页逻辑
}
#endregion
}
}

112
LTEMvcApp/README_LTEClient_Refactoring.md

@ -0,0 +1,112 @@
# LTEClient 重构说明
## 重构原因
### 1. 职责分离问题
**原始问题:**
- `LTEClient` 承担了过多职责,包括日志解析、存储、字符串ID管理、正则表达式处理等
- 与 `LogsManager` 存在功能重复,违反了单一职责原则
**重构后:**
- `LTEClient` 专注于客户端状态管理和日志解析
- `LogsManager` 负责全局日志管理和字符串ID管理
### 2. 重复功能消除
**原始重复功能:**
```csharp
// LTEClient 中的重复实现
private readonly Dictionary<string, int> _stringToIdCache = new();
private readonly Dictionary<int, string> _idToStringCache = new();
private int _stringIdCounter = 0;
public int StringToId(string str) { ... }
public string IdToString(int id) { ... }
```
**重构后:**
```csharp
// 委托给 LogsManager
public int StringToId(string str) => _logsManager.String2Id(str);
public string IdToString(int id) => _logsManager.Id2String(id);
```
### 3. 依赖注入改进
**原始问题:**
- `LTEClient` 独立管理字符串ID缓存
- 多个客户端实例可能产生不一致的ID映射
**重构后:**
- 通过依赖注入使用统一的 `LogsManager`
- 确保字符串ID在整个应用中的一致性
## 重构内容
### 1. 构造函数修改
```csharp
// 原始构造函数
public LTEClient(ClientConfig config)
public LTEClient(string name)
// 重构后构造函数
public LTEClient(ClientConfig config, LogsManager logsManager)
public LTEClient(string name, LogsManager logsManager)
```
### 2. 移除重复字段
- 移除了 `_stringToIdCache`、`_idToStringCache`、`_stringIdCounter` 等重复字段
- 添加了 `_logsManager` 依赖字段
### 3. 方法委托
- `StringToId()``IdToString()` 方法现在委托给 `LogsManager`
- 保持了原有的公共接口不变
## 使用建议
### 1. 创建客户端实例
```csharp
// 在服务中创建客户端
var logsManager = new LogsManager();
var clientConfig = new ClientConfig { Name = "TestClient" };
var client = new LTEClient(clientConfig, logsManager);
```
### 2. 依赖注入配置
```csharp
// 在 Startup.cs 或 Program.cs 中配置
services.AddSingleton<LogsManager>();
services.AddTransient<LTEClient>(provider =>
{
var logsManager = provider.GetService<LogsManager>();
var config = new ClientConfig { Name = "DefaultClient" };
return new LTEClient(config, logsManager);
});
```
### 3. 进一步优化建议
1. **日志解析分离**:考虑将日志解析逻辑提取到专门的 `LogParser` 类中
2. **状态管理**:考虑使用状态模式管理客户端的不同状态
3. **事件驱动**:使用事件机制通知日志状态变化
4. **配置管理**:将正则表达式等配置提取到配置文件中
## 优势
1. **代码复用**:消除了字符串ID管理的重复代码
2. **一致性**:确保字符串ID在整个应用中的一致性
3. **可测试性**:通过依赖注入更容易进行单元测试
4. **可维护性**:职责分离使代码更容易维护和扩展
5. **性能优化**:统一的字符串ID缓存减少了内存使用
## 注意事项
1. **向后兼容**:保持了原有的公共接口,不会影响现有代码
2. **全局状态**:`LogsManager` 的字符串ID缓存是全局的,在应用重启前不会重置
3. **线程安全**:需要注意多线程环境下的字符串ID管理
Loading…
Cancel
Save