11 changed files with 1023 additions and 1742 deletions
@ -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; } |
|||
} |
|||
} |
@ -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
|
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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 |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -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 |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -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; } |
|||
} |
|||
} |
@ -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}"; |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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(" ", " ") ?? ""; |
|||
} |
|||
|
|||
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
|
|||
} |
|||
} |
@ -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…
Reference in new issue