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