33 changed files with 1994 additions and 69 deletions
@ -0,0 +1,376 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using CellularManagement.Domain.Repositories.Device; |
||||
|
using CellularManagement.Domain.Repositories.Logging; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using X1.DynamicClientCore.Interfaces; |
||||
|
using X1.DynamicClientCore.Models; |
||||
|
using CellularManagement.Domain.Models; |
||||
|
using X1.Domain.Transmission; |
||||
|
using CellularManagement.Domain.Repositories.Base; |
||||
|
using System.Data; |
||||
|
|
||||
|
namespace X1.Application.BackendServiceManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 设备管理服务 - 负责管理设备端点的后台服务
|
||||
|
/// </summary>
|
||||
|
public class DeviceManagementService : BackgroundService |
||||
|
{ |
||||
|
private readonly ILogger<DeviceManagementService> _logger; |
||||
|
private readonly IServiceEndpointManager _endpointManager; |
||||
|
private readonly ICellularDeviceRepository _deviceRepository; |
||||
|
private readonly IProtocolChannelManager _protocolChannelManager; |
||||
|
private readonly IProtocolLogRepository _repository; |
||||
|
private readonly IUnitOfWork _unitOfWork; |
||||
|
|
||||
|
// 配置常量
|
||||
|
private const string DEFAULT_PROTOCOL = "http"; |
||||
|
private const string DEFAULT_BASE_PATH = "/api/v1"; |
||||
|
private const int DEFAULT_TIMEOUT = 10; |
||||
|
|
||||
|
public DeviceManagementService( |
||||
|
ICellularDeviceRepository deviceRepository, |
||||
|
IServiceEndpointManager endpointManager, |
||||
|
IProtocolChannelManager protocolChannelManager, |
||||
|
ILogger<DeviceManagementService> logger, |
||||
|
IProtocolLogRepository repository, |
||||
|
IUnitOfWork unitOfWork) |
||||
|
{ |
||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
||||
|
_endpointManager = endpointManager ?? throw new ArgumentNullException(nameof(endpointManager)); |
||||
|
_deviceRepository = deviceRepository ?? throw new ArgumentNullException(nameof(deviceRepository)); |
||||
|
_protocolChannelManager = protocolChannelManager ?? throw new ArgumentNullException(nameof(protocolChannelManager)); |
||||
|
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); |
||||
|
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 执行后台服务的主要逻辑
|
||||
|
/// </summary>
|
||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
_logger.LogInformation("DeviceManagementService started. Initializing device endpoints..."); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await InitializeDeviceEndpointsAsync(stoppingToken); |
||||
|
_logger.LogInformation("DeviceManagementService completed initialization."); |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("DeviceManagementService was cancelled during initialization."); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "Error occurred during device endpoint initialization."); |
||||
|
} |
||||
|
|
||||
|
// 启动协议日志处理循环
|
||||
|
_ = Task.Run(() => ProcessProtocolLogsAsync(stoppingToken), stoppingToken); |
||||
|
|
||||
|
// 服务初始化完成后,等待取消请求
|
||||
|
while (!stoppingToken.IsCancellationRequested) |
||||
|
{ |
||||
|
await Task.Delay(1000, stoppingToken); // 每秒检查一次取消请求
|
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("DeviceManagementService stopped."); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 处理协议日志的循环
|
||||
|
/// </summary>
|
||||
|
private async Task ProcessProtocolLogsAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
_logger.LogInformation("开始协议日志处理循环"); |
||||
|
|
||||
|
while (!stoppingToken.IsCancellationRequested) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// 从通道读取协议日志
|
||||
|
var protocolLogs = await _protocolChannelManager.ReadFromChannelAsync(stoppingToken); |
||||
|
|
||||
|
if (protocolLogs.Any()) |
||||
|
{ |
||||
|
await ProcessProtocolLogs(protocolLogs, stoppingToken); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 没有日志时短暂等待,避免空转
|
||||
|
await Task.Delay(100, stoppingToken); |
||||
|
} |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("协议日志处理循环被取消"); |
||||
|
break; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "处理协议日志时发生错误"); |
||||
|
await Task.Delay(1000, stoppingToken); // 错误时等待1秒再继续
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("协议日志处理循环已停止"); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 处理协议日志
|
||||
|
/// </summary>
|
||||
|
private async Task ProcessProtocolLogs(IEnumerable<ProtocolLog> protocolLogs, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
// 参数验证
|
||||
|
if (protocolLogs == null) |
||||
|
{ |
||||
|
_logger.LogWarning("接收到空的协议日志集合"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var logs = protocolLogs.ToList(); |
||||
|
if (!logs.Any()) |
||||
|
{ |
||||
|
_logger.LogDebug("协议日志集合为空,跳过处理"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_logger.LogDebug("开始处理协议日志,数量:{Count}", logs.Count); |
||||
|
|
||||
|
// 数据验证
|
||||
|
var validLogs = ValidateAndFilterLogs(logs); |
||||
|
if (!validLogs.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("没有有效的协议日志需要处理"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 使用事务处理
|
||||
|
using var transaction = await _unitOfWork.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); |
||||
|
try |
||||
|
{ |
||||
|
// 批量插入到数据库
|
||||
|
await _repository.AddRangeAsync(validLogs, cancellationToken); |
||||
|
|
||||
|
// 保存更改
|
||||
|
await _unitOfWork.SaveChangesAsync(cancellationToken); |
||||
|
|
||||
|
// 提交事务
|
||||
|
await _unitOfWork.CommitTransactionAsync(transaction, cancellationToken); |
||||
|
|
||||
|
_logger.LogDebug("协议日志批量插入数据库成功,数量:{Count}", validLogs.Count()); |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("协议日志处理被取消"); |
||||
|
await _unitOfWork.RollbackTransactionAsync(cancellationToken); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "批量插入协议日志到数据库失败,数量:{Count}", validLogs.Count()); |
||||
|
await _unitOfWork.RollbackTransactionAsync(cancellationToken); |
||||
|
|
||||
|
// 如果批量插入失败,尝试逐个插入
|
||||
|
await ProcessProtocolLogsIndividually(validLogs, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 验证和过滤协议日志
|
||||
|
/// </summary>
|
||||
|
private IEnumerable<ProtocolLog> ValidateAndFilterLogs(IEnumerable<ProtocolLog> logs) |
||||
|
{ |
||||
|
return logs.Where(log => |
||||
|
{ |
||||
|
if (log == null) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过null协议日志"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 验证必需字段
|
||||
|
if (string.IsNullOrWhiteSpace(log.DeviceCode)) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:设备代码为空,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(log.RuntimeCode)) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:运行时代码为空,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (log.Timestamp <= 0) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:时间戳无效,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (log.MessageId <= 0) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:消息ID无效,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 逐个处理协议日志(批量插入失败时的备用方案)
|
||||
|
/// </summary>
|
||||
|
private async Task ProcessProtocolLogsIndividually(IEnumerable<ProtocolLog> protocolLogs, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var logs = protocolLogs.ToList(); |
||||
|
var successCount = 0; |
||||
|
var errorCount = 0; |
||||
|
|
||||
|
_logger.LogInformation("开始逐个插入协议日志,总数:{Count}", logs.Count); |
||||
|
|
||||
|
// 分批处理,避免内存问题
|
||||
|
const int batchSize = 50; |
||||
|
for (int i = 0; i < logs.Count; i += batchSize) |
||||
|
{ |
||||
|
if (cancellationToken.IsCancellationRequested) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
var batch = logs.Skip(i).Take(batchSize); |
||||
|
using var transaction = await _unitOfWork.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
foreach (var log in batch) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await _repository.AddAsync(log, cancellationToken); |
||||
|
successCount++; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
errorCount++; |
||||
|
_logger.LogError(ex, "插入单个协议日志失败,ID:{LogId},设备:{DeviceCode}", log.Id, log.DeviceCode); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 保存当前批次的更改
|
||||
|
await _unitOfWork.SaveChangesAsync(cancellationToken); |
||||
|
await _unitOfWork.CommitTransactionAsync(transaction, cancellationToken); |
||||
|
|
||||
|
_logger.LogDebug("批次处理完成,成功:{SuccessCount},失败:{ErrorCount},批次大小:{BatchSize}", |
||||
|
successCount, errorCount, batch.Count()); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
await _unitOfWork.RollbackTransactionAsync(cancellationToken); |
||||
|
_logger.LogError(ex, "批次处理失败,批次索引:{BatchIndex}", i / batchSize); |
||||
|
errorCount += batch.Count(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("协议日志逐个插入完成,成功:{SuccessCount},失败:{ErrorCount},总数:{TotalCount}", |
||||
|
successCount, errorCount, logs.Count); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 处理单个协议日志
|
||||
|
/// </summary>
|
||||
|
private async Task ProcessSingleProtocolLog(ProtocolLog log, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
// 这里可以添加具体的协议日志处理逻辑
|
||||
|
// 例如:保存到数据库、发送通知、更新设备状态等
|
||||
|
|
||||
|
_logger.LogDebug("处理协议日志,ID:{Id},设备:{DeviceCode},运行时:{RuntimeCode},层类型:{LayerType}", |
||||
|
log.Id, log.DeviceCode, log.RuntimeCode, log.LayerType); |
||||
|
|
||||
|
// 示例:根据设备代码查找对应的端点进行处理
|
||||
|
var endpoint = _endpointManager.GetEndpoint(log.DeviceCode); |
||||
|
if (endpoint != null) |
||||
|
{ |
||||
|
// 可以在这里调用设备端点的API进行相关操作
|
||||
|
_logger.LogDebug("找到设备端点:{EndpointName},IP:{Ip},端口:{Port}", |
||||
|
endpoint.Name, endpoint.Ip, endpoint.Port); |
||||
|
} |
||||
|
|
||||
|
await Task.CompletedTask; // 占位符,实际处理逻辑待实现
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 初始化设备端点信息
|
||||
|
/// </summary>
|
||||
|
private async Task InitializeDeviceEndpointsAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
_logger.LogInformation("Initializing device endpoints..."); |
||||
|
|
||||
|
var devices = await _deviceRepository.GetDeviceBasicInfoListAsync(cancellationToken); |
||||
|
|
||||
|
if (devices == null || !devices.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("No devices found to initialize endpoints."); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var successCount = 0; |
||||
|
var skipCount = 0; |
||||
|
|
||||
|
foreach (var device in devices) |
||||
|
{ |
||||
|
if (IsValidDeviceInfo(device)) |
||||
|
{ |
||||
|
var endpoint = CreateServiceEndpoint(device); |
||||
|
_endpointManager.AddOrUpdateEndpoint(endpoint); |
||||
|
successCount++; |
||||
|
|
||||
|
_logger.LogDebug("Initialized endpoint for device: {DeviceCode} at {IpAddress}:{Port}", |
||||
|
device.DeviceCode, device.IpAddress, device.AgentPort); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_logger.LogWarning("Skipping invalid device: {DeviceCode}", device.DeviceCode); |
||||
|
skipCount++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("Device endpoint initialization completed. Success: {SuccessCount}, Skipped: {SkipCount}, Total: {TotalCount}", |
||||
|
successCount, skipCount, devices.Count); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 验证设备信息是否有效
|
||||
|
/// </summary>
|
||||
|
private static bool IsValidDeviceInfo(DeviceBasicInfo device) |
||||
|
{ |
||||
|
return device != null |
||||
|
&& !string.IsNullOrWhiteSpace(device.DeviceCode) |
||||
|
&& !string.IsNullOrWhiteSpace(device.IpAddress) |
||||
|
&& device.AgentPort > 0 |
||||
|
&& device.AgentPort <= 65535; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备信息创建服务端点
|
||||
|
/// </summary>
|
||||
|
private static ServiceEndpoint CreateServiceEndpoint(DeviceBasicInfo device) |
||||
|
{ |
||||
|
return new ServiceEndpoint |
||||
|
{ |
||||
|
Name = device.DeviceCode, |
||||
|
Ip = device.IpAddress, |
||||
|
Port = device.AgentPort, |
||||
|
Protocol = DEFAULT_PROTOCOL, |
||||
|
BasePath = DEFAULT_BASE_PATH, |
||||
|
Timeout = DEFAULT_TIMEOUT, |
||||
|
Enabled = true |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,159 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Threading.Channels; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using X1.Domain.Transmission; |
||||
|
|
||||
|
namespace X1.Application.BackendServiceManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 协议通道管理器实现
|
||||
|
/// 提供协议日志的读取、写入和清空功能
|
||||
|
/// </summary>
|
||||
|
public class ProtocolChannelManager : IProtocolChannelManager |
||||
|
{ |
||||
|
private readonly ILogger<ProtocolChannelManager> _logger; |
||||
|
private readonly Channel<ProtocolLog[]> _channel; |
||||
|
|
||||
|
public ProtocolChannelManager(ILogger<ProtocolChannelManager> logger) |
||||
|
{ |
||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
||||
|
_channel = Channel.CreateUnbounded<ProtocolLog[]>(new UnboundedChannelOptions |
||||
|
{ |
||||
|
SingleReader = false, |
||||
|
SingleWriter = false, |
||||
|
AllowSynchronousContinuations = false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 从通道读取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志集合</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> ReadFromChannelAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// 等待有数据可读
|
||||
|
if (await _channel.Reader.WaitToReadAsync(cancellationToken)) |
||||
|
{ |
||||
|
// 读取一个数组
|
||||
|
if (_channel.Reader.TryRead(out var logsArray)) |
||||
|
{ |
||||
|
_logger.LogDebug("从通道读取协议日志数组,数量:{Count}", logsArray.Length); |
||||
|
return logsArray; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Enumerable.Empty<ProtocolLog>(); |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("读取协议日志操作被取消"); |
||||
|
return Enumerable.Empty<ProtocolLog>(); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "读取协议日志时发生错误"); |
||||
|
return Enumerable.Empty<ProtocolLog>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 向通道写入协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="protocolLogs">协议日志集合</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>写入结果</returns>
|
||||
|
public async Task<bool> WriteToChannelAsync(IEnumerable<ProtocolLog> protocolLogs, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (protocolLogs == null) |
||||
|
{ |
||||
|
_logger.LogWarning("尝试写入空的协议日志集合"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var logs = protocolLogs.ToList(); |
||||
|
if (!logs.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("协议日志集合为空,跳过写入"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 过滤掉null的日志
|
||||
|
var validLogs = logs.Where(log => log != null).ToArray(); |
||||
|
|
||||
|
if (validLogs.Length == 0) |
||||
|
{ |
||||
|
_logger.LogWarning("没有有效的协议日志,跳过写入"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 批量写入数组
|
||||
|
await _channel.Writer.WriteAsync(validLogs, cancellationToken); |
||||
|
|
||||
|
_logger.LogDebug("写入协议日志数组到通道,数量:{Count}", validLogs.Length); |
||||
|
return true; |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("写入协议日志操作被取消"); |
||||
|
return false; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "写入协议日志时发生错误"); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 清空协议通道
|
||||
|
/// </summary>
|
||||
|
/// <returns>清空结果</returns>
|
||||
|
public async Task<bool> ClearChannelAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var count = 0; |
||||
|
|
||||
|
// 读取并丢弃所有数组直到通道为空
|
||||
|
while (await _channel.Reader.WaitToReadAsync()) |
||||
|
{ |
||||
|
if (_channel.Reader.TryRead(out var logsArray)) |
||||
|
{ |
||||
|
count += logsArray.Length; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("清空协议通道,清空日志数量:{Count}", count); |
||||
|
return true; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "清空协议通道时发生错误"); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 获取当前通道中的日志数量(估算)
|
||||
|
/// </summary>
|
||||
|
/// <returns>日志数量</returns>
|
||||
|
public int GetChannelCount() |
||||
|
{ |
||||
|
return _channel.Reader.Count; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
using CellularManagement.Domain.Common; |
||||
|
using CellularManagement.Domain.Entities.Device; |
||||
|
using MediatR; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志查询
|
||||
|
/// </summary>
|
||||
|
public class GetProtocolLogsByDeviceQuery : IRequest<OperationResult<GetProtocolLogsByDeviceResponse>> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 设备代码
|
||||
|
/// </summary>
|
||||
|
[Required(ErrorMessage = "设备代码不能为空")] |
||||
|
[MaxLength(50, ErrorMessage = "设备代码不能超过50个字符")] |
||||
|
public string DeviceCode { get; set; } = string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 开始时间戳
|
||||
|
/// </summary>
|
||||
|
public long? StartTimestamp { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 结束时间戳
|
||||
|
/// </summary>
|
||||
|
public long? EndTimestamp { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议层类型
|
||||
|
/// </summary>
|
||||
|
[MaxLength(50, ErrorMessage = "协议层类型不能超过50个字符")] |
||||
|
public string? LayerType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 设备运行时状态
|
||||
|
/// 当设置为 Running 时,只获取当前运行的数据
|
||||
|
/// </summary>
|
||||
|
public DeviceRuntimeStatus? DeviceRuntimeStatus { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 是否按时间戳降序排序
|
||||
|
/// </summary>
|
||||
|
public bool OrderByDescending { get; set; } = true; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,134 @@ |
|||||
|
using MediatR; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using CellularManagement.Domain.Common; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using CellularManagement.Domain.Entities.Device; |
||||
|
using CellularManagement.Domain.Repositories.Logging; |
||||
|
using CellularManagement.Domain.Repositories.Device; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志查询处理器
|
||||
|
/// </summary>
|
||||
|
public class GetProtocolLogsByDeviceQueryHandler : IRequestHandler<GetProtocolLogsByDeviceQuery, OperationResult<GetProtocolLogsByDeviceResponse>> |
||||
|
{ |
||||
|
private readonly IProtocolLogRepository _protocolLogRepository; |
||||
|
private readonly ICellularDeviceRuntimeRepository _deviceRuntimeRepository; |
||||
|
private readonly ILogger<GetProtocolLogsByDeviceQueryHandler> _logger; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 初始化查询处理器
|
||||
|
/// </summary>
|
||||
|
public GetProtocolLogsByDeviceQueryHandler( |
||||
|
IProtocolLogRepository protocolLogRepository, |
||||
|
ICellularDeviceRuntimeRepository deviceRuntimeRepository, |
||||
|
ILogger<GetProtocolLogsByDeviceQueryHandler> logger) |
||||
|
{ |
||||
|
_protocolLogRepository = protocolLogRepository; |
||||
|
_deviceRuntimeRepository = deviceRuntimeRepository; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 处理根据设备代码获取协议日志查询
|
||||
|
/// </summary>
|
||||
|
public async Task<OperationResult<GetProtocolLogsByDeviceResponse>> Handle(GetProtocolLogsByDeviceQuery request, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// 验证请求参数
|
||||
|
var validationContext = new ValidationContext(request); |
||||
|
var validationResults = new List<ValidationResult>(); |
||||
|
if (!Validator.TryValidateObject(request, validationContext, validationResults, true)) |
||||
|
{ |
||||
|
var errorMessages = validationResults.Select(r => r.ErrorMessage).ToList(); |
||||
|
_logger.LogWarning("请求参数无效: {Errors}", string.Join(", ", errorMessages)); |
||||
|
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateFailure(errorMessages); |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("开始获取设备 {DeviceCode} 的协议日志,运行时状态: {DeviceRuntimeStatus}", |
||||
|
request.DeviceCode, request.DeviceRuntimeStatus); |
||||
|
|
||||
|
// 获取设备运行时状态(仅在需要时查询)
|
||||
|
IEnumerable<string>? runtimeCodes = null; |
||||
|
if (request.DeviceRuntimeStatus.HasValue) |
||||
|
{ |
||||
|
var deviceRuntimes = await _deviceRuntimeRepository.GetRuntimesByDeviceCodeAsync( |
||||
|
request.DeviceCode, |
||||
|
cancellationToken); |
||||
|
|
||||
|
if (!deviceRuntimes.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("设备 {DeviceCode} 的运行时状态不存在", request.DeviceCode); |
||||
|
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateFailure($"设备 {request.DeviceCode} 的运行时状态不存在"); |
||||
|
} |
||||
|
|
||||
|
// 过滤出匹配状态的运行时
|
||||
|
var matchingRuntimes = deviceRuntimes.Where(r => r.RuntimeStatus == request.DeviceRuntimeStatus.Value).ToList(); |
||||
|
if (!matchingRuntimes.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("设备 {DeviceCode} 的运行时状态不匹配,期望: {ExpectedStatus}", |
||||
|
request.DeviceCode, request.DeviceRuntimeStatus.Value); |
||||
|
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateFailure( |
||||
|
$"设备 {request.DeviceCode} 的运行时状态不匹配,期望: {request.DeviceRuntimeStatus.Value}"); |
||||
|
} |
||||
|
|
||||
|
// 获取运行时编码集合
|
||||
|
runtimeCodes = matchingRuntimes.Select(r => r.RuntimeCode).ToList(); |
||||
|
_logger.LogInformation("使用运行时编码集合 {RuntimeCodes} 过滤协议日志", string.Join(", ", runtimeCodes)); |
||||
|
} |
||||
|
|
||||
|
// 使用 JOIN 高性能查询方法
|
||||
|
var protocolLogs = await _protocolLogRepository.GetByDeviceWithFiltersAsync( |
||||
|
request.DeviceCode, |
||||
|
runtimeCodes, |
||||
|
request.StartTimestamp, |
||||
|
request.EndTimestamp, |
||||
|
request.LayerType, |
||||
|
request.OrderByDescending, |
||||
|
cancellationToken); |
||||
|
|
||||
|
// 转换为DTO
|
||||
|
var protocolLogDtos = protocolLogs.Select(log => new ProtocolLogDto |
||||
|
{ |
||||
|
Id = log.Id, |
||||
|
MessageId = log.MessageId, |
||||
|
LayerType = (int)log.LayerType, |
||||
|
MessageDetailJson = log.MessageDetailJson, |
||||
|
CellID = log.CellID, |
||||
|
IMSI = log.IMSI, |
||||
|
Direction = log.Direction, |
||||
|
UEID = log.UEID, |
||||
|
PLMN = log.PLMN, |
||||
|
TimeMs = log.TimeMs, |
||||
|
Timestamp = log.Timestamp, |
||||
|
Info = log.Info, |
||||
|
Message = log.Message, |
||||
|
DeviceCode = log.DeviceCode, |
||||
|
RuntimeCode = log.RuntimeCode, |
||||
|
MessageDetail = log.MessageDetail, |
||||
|
Time = log.Time |
||||
|
}).ToList(); |
||||
|
|
||||
|
// 构建响应
|
||||
|
var response = new GetProtocolLogsByDeviceResponse |
||||
|
{ |
||||
|
DeviceCode = request.DeviceCode, |
||||
|
Items = protocolLogDtos.ToList() |
||||
|
}; |
||||
|
|
||||
|
_logger.LogInformation("成功获取设备 {DeviceCode} 的协议日志,共 {Count} 条记录", |
||||
|
request.DeviceCode, protocolLogDtos.Count); |
||||
|
|
||||
|
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateSuccess(response); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取设备 {DeviceCode} 的协议日志时发生错误", request.DeviceCode); |
||||
|
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateFailure($"获取协议日志时发生错误: {ex.Message}"); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,112 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志响应
|
||||
|
/// </summary>
|
||||
|
public class GetProtocolLogsByDeviceResponse |
||||
|
{ |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 设备代码
|
||||
|
/// </summary>
|
||||
|
public string DeviceCode { get; set; } = string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志列表
|
||||
|
/// </summary>
|
||||
|
public List<ProtocolLogDto> Items { get; set; } = new(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志数据传输对象
|
||||
|
/// </summary>
|
||||
|
public class ProtocolLogDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 主键ID
|
||||
|
/// </summary>
|
||||
|
public string Id { get; set; } = string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息ID
|
||||
|
/// </summary>
|
||||
|
public long MessageId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议层类型
|
||||
|
/// </summary>
|
||||
|
public int LayerType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息详情集合(JSON格式存储)
|
||||
|
/// </summary>
|
||||
|
public string? MessageDetailJson { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 小区ID
|
||||
|
/// </summary>
|
||||
|
public int? CellID { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 国际移动用户识别码
|
||||
|
/// </summary>
|
||||
|
public string? IMSI { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 日志方向类型
|
||||
|
/// </summary>
|
||||
|
public int Direction { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 用户设备ID
|
||||
|
/// </summary>
|
||||
|
public int? UEID { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 公共陆地移动网络标识
|
||||
|
/// </summary>
|
||||
|
public string? PLMN { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间间隔(毫秒)
|
||||
|
/// </summary>
|
||||
|
public long TimeMs { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间戳
|
||||
|
/// </summary>
|
||||
|
public long Timestamp { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 信息字段
|
||||
|
/// </summary>
|
||||
|
public string? Info { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息字段
|
||||
|
/// </summary>
|
||||
|
public string? Message { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 设备代码
|
||||
|
/// </summary>
|
||||
|
public string DeviceCode { get; set; } = string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 运行时代码
|
||||
|
/// </summary>
|
||||
|
public string RuntimeCode { get; set; } = string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息详情集合(用于业务逻辑)
|
||||
|
/// </summary>
|
||||
|
public IEnumerable<string>? MessageDetail { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间间隔(用于业务逻辑)
|
||||
|
/// </summary>
|
||||
|
public TimeSpan Time { get; set; } |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace X1.Domain.Entities.Logging |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 协议层类型枚举
|
||||
|
/// 定义了各种协议层的类型标识
|
||||
|
/// </summary>
|
||||
|
public enum ProtocolLayer |
||||
|
{ |
||||
|
NONE, |
||||
|
GTPU, |
||||
|
LPPa, |
||||
|
M2AP, |
||||
|
MAC, |
||||
|
NAS, |
||||
|
NGAP, |
||||
|
NRPPa, |
||||
|
PDCP, |
||||
|
PROD, |
||||
|
PHY, |
||||
|
RLC, |
||||
|
RRC, |
||||
|
S1AP, |
||||
|
TRX, |
||||
|
X2AP, |
||||
|
XnAP, |
||||
|
IP, |
||||
|
IMS, |
||||
|
CX, |
||||
|
RX, |
||||
|
S6, |
||||
|
S13, |
||||
|
SGsAP, |
||||
|
SBcAP, |
||||
|
LCSAP, |
||||
|
N12, |
||||
|
N8, |
||||
|
N17, |
||||
|
N50, |
||||
|
N13, |
||||
|
NL1, |
||||
|
HTTP2, |
||||
|
EPDG, |
||||
|
IKEV2, |
||||
|
IPSEC, |
||||
|
MEDIA, |
||||
|
MMS, |
||||
|
SIP, |
||||
|
} |
||||
|
} |
@ -0,0 +1,159 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using CellularManagement.Domain.Abstractions; |
||||
|
using CellularManagement.Domain.Entities.Common; |
||||
|
using X1.Domain.Entities.Logging; |
||||
|
|
||||
|
namespace CellularManagement.Domain.Entities.Logging; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志实体
|
||||
|
/// 用于存储协议层的日志数据
|
||||
|
/// </summary>
|
||||
|
public class ProtocolLog : Entity |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 消息ID
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public long MessageId { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议层类型
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[MaxLength(50)] |
||||
|
public ProtocolLayer LayerType { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息详情集合(JSON格式存储)
|
||||
|
/// </summary>
|
||||
|
public string? MessageDetailJson { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 小区ID
|
||||
|
/// </summary>
|
||||
|
public int? CellID { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 国际移动用户识别码
|
||||
|
/// </summary>
|
||||
|
[MaxLength(50)] |
||||
|
public string? IMSI { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 日志方向类型
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public int Direction { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 用户设备ID
|
||||
|
/// </summary>
|
||||
|
public int? UEID { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 公共陆地移动网络标识
|
||||
|
/// </summary>
|
||||
|
[MaxLength(20)] |
||||
|
public string? PLMN { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间间隔(毫秒)
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public long TimeMs { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间戳
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public long Timestamp { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 信息字段
|
||||
|
/// </summary>
|
||||
|
[MaxLength(500)] |
||||
|
public string? Info { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息字段
|
||||
|
/// </summary>
|
||||
|
[MaxLength(1000)] |
||||
|
public string? Message { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 设备代码
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[MaxLength(50)] |
||||
|
public string DeviceCode { get; private set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 运行时代码
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[MaxLength(50)] |
||||
|
public string RuntimeCode { get; private set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 私有构造函数
|
||||
|
/// </summary>
|
||||
|
private ProtocolLog() { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 创建协议日志
|
||||
|
/// </summary>
|
||||
|
public static ProtocolLog Create( |
||||
|
long messageId, |
||||
|
int layerType, |
||||
|
int direction, |
||||
|
long timeMs, |
||||
|
long timestamp, |
||||
|
string deviceCode, |
||||
|
string runtimeCode, |
||||
|
string? messageDetailJson = null, |
||||
|
int? cellID = null, |
||||
|
string? imsi = null, |
||||
|
int? ueid = null, |
||||
|
string? plmn = null, |
||||
|
string? info = null, |
||||
|
string? message = null) |
||||
|
{ |
||||
|
return new ProtocolLog |
||||
|
{ |
||||
|
Id = Guid.NewGuid().ToString(), |
||||
|
MessageId = messageId, |
||||
|
LayerType = (ProtocolLayer)layerType, |
||||
|
MessageDetailJson = messageDetailJson, |
||||
|
CellID = cellID, |
||||
|
IMSI = imsi, |
||||
|
Direction = direction, |
||||
|
UEID = ueid, |
||||
|
PLMN = plmn, |
||||
|
TimeMs = timeMs, |
||||
|
Timestamp = timestamp, |
||||
|
Info = info, |
||||
|
Message = message, |
||||
|
DeviceCode = deviceCode, |
||||
|
RuntimeCode = runtimeCode |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 消息详情集合(用于业务逻辑)
|
||||
|
/// </summary>
|
||||
|
public IEnumerable<string>? MessageDetail |
||||
|
{ |
||||
|
get => !string.IsNullOrEmpty(MessageDetailJson) |
||||
|
? System.Text.Json.JsonSerializer.Deserialize<IEnumerable<string>>(MessageDetailJson) |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 时间间隔(用于业务逻辑)
|
||||
|
/// </summary>
|
||||
|
public TimeSpan Time |
||||
|
{ |
||||
|
get => TimeSpan.FromMilliseconds(TimeMs); |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
namespace CellularManagement.Domain.Models; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 设备基本信息模型
|
||||
|
/// </summary>
|
||||
|
public class DeviceBasicInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 设备编码
|
||||
|
/// </summary>
|
||||
|
public string DeviceCode { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// IP地址
|
||||
|
/// </summary>
|
||||
|
public string IpAddress { get; set; } = null!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Agent端口
|
||||
|
/// </summary>
|
||||
|
public int AgentPort { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 默认构造函数
|
||||
|
/// </summary>
|
||||
|
public DeviceBasicInfo() { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 创建设备基本信息实例
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备编码</param>
|
||||
|
/// <param name="ipAddress">IP地址</param>
|
||||
|
/// <param name="agentPort">Agent端口</param>
|
||||
|
/// <returns>设备基本信息实例</returns>
|
||||
|
public static DeviceBasicInfo Create(string deviceCode, string ipAddress, int agentPort) |
||||
|
{ |
||||
|
return new DeviceBasicInfo |
||||
|
{ |
||||
|
DeviceCode = deviceCode, |
||||
|
IpAddress = ipAddress, |
||||
|
AgentPort = agentPort |
||||
|
}; |
||||
|
} |
||||
|
} |
@ -0,0 +1,75 @@ |
|||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using CellularManagement.Domain.Repositories.Base; |
||||
|
using X1.Domain.Entities.Logging; |
||||
|
|
||||
|
namespace CellularManagement.Domain.Repositories.Logging; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志仓储接口
|
||||
|
/// </summary>
|
||||
|
public interface IProtocolLogRepository : IBaseRepository<ProtocolLog> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据运行时代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="runtimeCode">运行时代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByRuntimeCodeAsync(string runtimeCode, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码和运行时代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="runtimeCode">运行时代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByDeviceAndRuntimeCodeAsync(string deviceCode, string runtimeCode, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据时间范围获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="startTimestamp">开始时间戳</param>
|
||||
|
/// <param name="endTimestamp">结束时间戳</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByTimeRangeAsync(long startTimestamp, long endTimestamp, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据协议层类型获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="layerType">协议层类型</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByLayerTypeAsync(ProtocolLayer layerType, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码和运行时状态获取协议日志(高性能查询)
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="runtimeCodes">运行时代码集合</param>
|
||||
|
/// <param name="startTimestamp">开始时间戳</param>
|
||||
|
/// <param name="endTimestamp">结束时间戳</param>
|
||||
|
/// <param name="layerType">协议层类型</param>
|
||||
|
/// <param name="orderByDescending">是否按时间戳降序排序</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> GetByDeviceWithFiltersAsync( |
||||
|
string deviceCode, |
||||
|
IEnumerable<string>? runtimeCodes = null, |
||||
|
long? startTimestamp = null, |
||||
|
long? endTimestamp = null, |
||||
|
string? layerType = null, |
||||
|
bool orderByDescending = true, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
|
||||
|
namespace X1.Domain.Transmission |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 协议通道管理器接口
|
||||
|
/// 提供协议日志的读取、写入和清空功能
|
||||
|
/// </summary>
|
||||
|
public interface IProtocolChannelManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 从通道读取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志集合</returns>
|
||||
|
Task<IEnumerable<ProtocolLog>> ReadFromChannelAsync(CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 向通道写入协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="protocolLogs">协议日志集合</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>写入结果</returns>
|
||||
|
Task<bool> WriteToChannelAsync(IEnumerable<ProtocolLog> protocolLogs, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 清空协议通道
|
||||
|
/// </summary>
|
||||
|
/// <returns>清空结果</returns>
|
||||
|
Task<bool> ClearChannelAsync(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using X1.Domain.Models; |
||||
|
|
||||
|
namespace X1.Domain.Transmission |
||||
|
{ |
||||
|
public interface IProtocolLogObserver |
||||
|
{ |
||||
|
public Task OnProtocolLogsReceived(IEnumerable<MessageTransferProtocolLog> logDetails); |
||||
|
} |
||||
|
} |
@ -0,0 +1,94 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
|
||||
|
namespace CellularManagement.Infrastructure.Configurations.Logging; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ProtocolLog 实体配置类
|
||||
|
/// 用于配置协议日志实体在数据库中的映射关系
|
||||
|
/// </summary>
|
||||
|
public sealed class ProtocolLogConfiguration : IEntityTypeConfiguration<ProtocolLog> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 配置 ProtocolLog 实体
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">实体类型构建器</param>
|
||||
|
public void Configure(EntityTypeBuilder<ProtocolLog> builder) |
||||
|
{ |
||||
|
// 配置表名
|
||||
|
builder.ToTable("tb_ProtocolLog", t => t.HasComment("协议日志表")); |
||||
|
|
||||
|
// 配置主键
|
||||
|
builder.HasKey(p => p.Id); |
||||
|
|
||||
|
// 配置索引
|
||||
|
builder.HasIndex(p => p.MessageId).HasDatabaseName("IX_ProtocolLog_MessageId"); |
||||
|
builder.HasIndex(p => p.DeviceCode).HasDatabaseName("IX_ProtocolLog_DeviceCode"); |
||||
|
builder.HasIndex(p => p.RuntimeCode).HasDatabaseName("IX_ProtocolLog_RuntimeCode"); |
||||
|
builder.HasIndex(p => p.Timestamp).HasDatabaseName("IX_ProtocolLog_Timestamp"); |
||||
|
builder.HasIndex(p => p.LayerType).HasDatabaseName("IX_ProtocolLog_LayerType"); |
||||
|
builder.HasIndex(p => new { p.DeviceCode, p.RuntimeCode }).HasDatabaseName("IX_ProtocolLog_DeviceCode_RuntimeCode"); |
||||
|
builder.HasIndex(p => new { p.DeviceCode, p.Timestamp }).HasDatabaseName("IX_ProtocolLog_DeviceCode_Timestamp"); |
||||
|
|
||||
|
// 配置属性
|
||||
|
builder.Property(p => p.Id) |
||||
|
.HasComment("主键ID"); |
||||
|
|
||||
|
builder.Property(p => p.MessageId) |
||||
|
.IsRequired() |
||||
|
.HasComment("消息ID"); |
||||
|
|
||||
|
builder.Property(p => p.LayerType) |
||||
|
.IsRequired() |
||||
|
.HasMaxLength(50) |
||||
|
.HasComment("协议层类型"); |
||||
|
|
||||
|
builder.Property(p => p.MessageDetailJson) |
||||
|
.HasComment("消息详情集合(JSON格式存储)"); |
||||
|
|
||||
|
builder.Property(p => p.CellID) |
||||
|
.HasComment("小区ID"); |
||||
|
|
||||
|
builder.Property(p => p.IMSI) |
||||
|
.HasMaxLength(50) |
||||
|
.HasComment("国际移动用户识别码"); |
||||
|
|
||||
|
builder.Property(p => p.Direction) |
||||
|
.IsRequired() |
||||
|
.HasComment("日志方向类型"); |
||||
|
|
||||
|
builder.Property(p => p.UEID) |
||||
|
.HasComment("用户设备ID"); |
||||
|
|
||||
|
builder.Property(p => p.PLMN) |
||||
|
.HasMaxLength(20) |
||||
|
.HasComment("公共陆地移动网络标识"); |
||||
|
|
||||
|
builder.Property(p => p.TimeMs) |
||||
|
.IsRequired() |
||||
|
.HasComment("时间间隔(毫秒)"); |
||||
|
|
||||
|
builder.Property(p => p.Timestamp) |
||||
|
.IsRequired() |
||||
|
.HasComment("时间戳"); |
||||
|
|
||||
|
builder.Property(p => p.Info) |
||||
|
.HasMaxLength(500) |
||||
|
.HasComment("信息字段"); |
||||
|
|
||||
|
builder.Property(p => p.Message) |
||||
|
.HasMaxLength(1000) |
||||
|
.HasComment("消息字段"); |
||||
|
|
||||
|
builder.Property(p => p.DeviceCode) |
||||
|
.IsRequired() |
||||
|
.HasMaxLength(50) |
||||
|
.HasComment("设备代码"); |
||||
|
|
||||
|
builder.Property(p => p.RuntimeCode) |
||||
|
.IsRequired() |
||||
|
.HasMaxLength(50) |
||||
|
.HasComment("运行时代码"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,226 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using CellularManagement.Domain.Entities.Device; |
||||
|
using CellularManagement.Domain.Repositories.Logging; |
||||
|
using CellularManagement.Domain.Repositories.Base; |
||||
|
using CellularManagement.Infrastructure.Repositories.Base; |
||||
|
using System.Linq; |
||||
|
using X1.Domain.Entities.Logging; |
||||
|
|
||||
|
namespace CellularManagement.Infrastructure.Repositories.Logging; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志仓储实现类
|
||||
|
/// </summary>
|
||||
|
public class ProtocolLogRepository : BaseRepository<ProtocolLog>, IProtocolLogRepository |
||||
|
{ |
||||
|
private readonly ILogger<ProtocolLogRepository> _logger; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 构造函数
|
||||
|
/// </summary>
|
||||
|
/// <param name="commandRepository">命令仓储</param>
|
||||
|
/// <param name="queryRepository">查询仓储</param>
|
||||
|
/// <param name="logger">日志记录器</param>
|
||||
|
public ProtocolLogRepository( |
||||
|
ICommandRepository<ProtocolLog> commandRepository, |
||||
|
IQueryRepository<ProtocolLog> queryRepository, |
||||
|
ILogger<ProtocolLogRepository> logger) |
||||
|
: base(commandRepository, queryRepository, logger) |
||||
|
{ |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var logs = await QueryRepository.FindAsync( |
||||
|
p => p.DeviceCode == deviceCode, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
return logs.OrderByDescending(p => p.Timestamp); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取设备代码 {DeviceCode} 的协议日志时发生错误", deviceCode); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据运行时代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="runtimeCode">运行时代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByRuntimeCodeAsync(string runtimeCode, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var logs = await QueryRepository.FindAsync( |
||||
|
p => p.RuntimeCode == runtimeCode, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
return logs.OrderByDescending(p => p.Timestamp); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取运行时代码 {RuntimeCode} 的协议日志时发生错误", runtimeCode); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码和运行时代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="runtimeCode">运行时代码</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByDeviceAndRuntimeCodeAsync(string deviceCode, string runtimeCode, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var logs = await QueryRepository.FindAsync( |
||||
|
p => p.DeviceCode == deviceCode && p.RuntimeCode == runtimeCode, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
return logs.OrderByDescending(p => p.Timestamp); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取设备代码 {DeviceCode} 和运行时代码 {RuntimeCode} 的协议日志时发生错误", deviceCode, runtimeCode); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据时间范围获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="startTimestamp">开始时间戳</param>
|
||||
|
/// <param name="endTimestamp">结束时间戳</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByTimeRangeAsync(long startTimestamp, long endTimestamp, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var logs = await QueryRepository.FindAsync( |
||||
|
p => p.Timestamp >= startTimestamp && p.Timestamp <= endTimestamp, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
return logs.OrderByDescending(p => p.Timestamp); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取时间范围 {StartTimestamp} 到 {EndTimestamp} 的协议日志时发生错误", startTimestamp, endTimestamp); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据协议层类型获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="layerType">协议层类型</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByLayerTypeAsync(ProtocolLayer layerType, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var logs = await QueryRepository.FindAsync( |
||||
|
p => p.LayerType == layerType, |
||||
|
cancellationToken: cancellationToken); |
||||
|
|
||||
|
return logs.OrderByDescending(p => p.Timestamp); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取协议层类型 {LayerType} 的协议日志时发生错误", layerType); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码和运行时状态获取协议日志(高性能查询)
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="runtimeCodes">运行时代码集合</param>
|
||||
|
/// <param name="startTimestamp">开始时间戳</param>
|
||||
|
/// <param name="endTimestamp">结束时间戳</param>
|
||||
|
/// <param name="layerType">协议层类型</param>
|
||||
|
/// <param name="orderByDescending">是否按时间戳降序排序</param>
|
||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
public async Task<IEnumerable<ProtocolLog>> GetByDeviceWithFiltersAsync( |
||||
|
string deviceCode, |
||||
|
IEnumerable<string>? runtimeCodes = null, |
||||
|
long? startTimestamp = null, |
||||
|
long? endTimestamp = null, |
||||
|
string? layerType = null, |
||||
|
bool orderByDescending = true, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// 构建 SQL 查询
|
||||
|
var sql = @"
|
||||
|
SELECT pl.* |
||||
|
FROM ""ProtocolLogs"" pl |
||||
|
INNER JOIN ""CellularDeviceRuntimes"" cdr |
||||
|
ON pl.""DeviceCode"" = cdr.""DeviceCode"" |
||||
|
AND pl.""RuntimeCode"" = cdr.""RuntimeCode"" |
||||
|
WHERE pl.""DeviceCode"" = @deviceCode";
|
||||
|
|
||||
|
var parameters = new List<object> { deviceCode }; |
||||
|
|
||||
|
// 添加运行时编码过滤
|
||||
|
if (runtimeCodes != null && runtimeCodes.Any()) |
||||
|
{ |
||||
|
var runtimeCodeList = string.Join(",", runtimeCodes.Select((_, i) => $"@runtimeCode{i}")); |
||||
|
sql += $" AND pl.\"RuntimeCode\" IN ({runtimeCodeList})"; |
||||
|
parameters.AddRange(runtimeCodes); |
||||
|
} |
||||
|
|
||||
|
// 添加时间范围过滤
|
||||
|
if (startTimestamp.HasValue) |
||||
|
{ |
||||
|
sql += " AND pl.\"Timestamp\" >= @startTimestamp"; |
||||
|
parameters.Add(startTimestamp.Value); |
||||
|
} |
||||
|
|
||||
|
if (endTimestamp.HasValue) |
||||
|
{ |
||||
|
sql += " AND pl.\"Timestamp\" <= @endTimestamp"; |
||||
|
parameters.Add(endTimestamp.Value); |
||||
|
} |
||||
|
|
||||
|
// 添加协议层类型过滤
|
||||
|
if (!string.IsNullOrEmpty(layerType)) |
||||
|
{ |
||||
|
sql += " AND pl.\"LayerType\" = @layerType"; |
||||
|
parameters.Add(layerType); |
||||
|
} |
||||
|
|
||||
|
// 添加排序
|
||||
|
sql += orderByDescending ? " ORDER BY pl.\"Timestamp\" DESC" : " ORDER BY pl.\"Timestamp\" ASC"; |
||||
|
|
||||
|
// 执行 SQL 查询
|
||||
|
var logs = await QueryRepository.ExecuteSqlQueryAsync<ProtocolLog>(sql, parameters.ToArray(), cancellationToken); |
||||
|
return logs; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "获取设备代码 {DeviceCode} 的协议日志时发生错误", deviceCode); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,78 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice; |
||||
|
using CellularManagement.Domain.Common; |
||||
|
using CellularManagement.Domain.Entities.Device; |
||||
|
using CellularManagement.Presentation.Abstractions; |
||||
|
using MediatR; |
||||
|
|
||||
|
namespace CellularManagement.Presentation.Controllers; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 协议日志控制器
|
||||
|
/// </summary>
|
||||
|
[ApiController] |
||||
|
[Route("api/[controller]")]
|
||||
|
[Authorize] |
||||
|
public class ProtocolLogsController : ApiController |
||||
|
{ |
||||
|
private readonly ILogger<ProtocolLogsController> _logger; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 构造函数
|
||||
|
/// </summary>
|
||||
|
/// <param name="mediator">中介者</param>
|
||||
|
/// <param name="logger">日志记录器</param>
|
||||
|
public ProtocolLogsController(IMediator mediator, ILogger<ProtocolLogsController> logger) |
||||
|
: base(mediator) |
||||
|
{ |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 根据设备代码获取协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="deviceCode">设备代码</param>
|
||||
|
/// <param name="startTimestamp">开始时间戳</param>
|
||||
|
/// <param name="endTimestamp">结束时间戳</param>
|
||||
|
/// <param name="layerType">协议层类型</param>
|
||||
|
/// <param name="deviceRuntimeStatus">设备运行时状态</param>
|
||||
|
/// <param name="orderByDescending">是否按时间戳降序排序</param>
|
||||
|
/// <returns>协议日志列表</returns>
|
||||
|
[HttpGet("device/{deviceCode}")] |
||||
|
public async Task<OperationResult<GetProtocolLogsByDeviceResponse>> GetProtocolLogsByDevice( |
||||
|
string deviceCode, |
||||
|
[FromQuery] long? startTimestamp = null, |
||||
|
[FromQuery] long? endTimestamp = null, |
||||
|
[FromQuery] string? layerType = null, |
||||
|
[FromQuery] int? deviceRuntimeStatus = null, |
||||
|
[FromQuery] bool orderByDescending = true) |
||||
|
{ |
||||
|
_logger.LogInformation("开始获取设备 {DeviceCode} 的协议日志,开始时间戳: {StartTimestamp}, 结束时间戳: {EndTimestamp}, 协议层类型: {LayerType}, 运行时状态: {DeviceRuntimeStatus}", |
||||
|
deviceCode, startTimestamp, endTimestamp, layerType, deviceRuntimeStatus); |
||||
|
|
||||
|
var query = new GetProtocolLogsByDeviceQuery |
||||
|
{ |
||||
|
DeviceCode = deviceCode, |
||||
|
StartTimestamp = startTimestamp, |
||||
|
EndTimestamp = endTimestamp, |
||||
|
LayerType = layerType, |
||||
|
DeviceRuntimeStatus = deviceRuntimeStatus.HasValue ? (DeviceRuntimeStatus)deviceRuntimeStatus.Value : null, |
||||
|
OrderByDescending = orderByDescending |
||||
|
}; |
||||
|
|
||||
|
var result = await mediator.Send(query); |
||||
|
if (!result.IsSuccess) |
||||
|
{ |
||||
|
_logger.LogWarning("获取设备 {DeviceCode} 的协议日志失败: {Message}", deviceCode, result.ErrorMessages); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
_logger.LogInformation("成功获取设备 {DeviceCode} 的协议日志,共 {Count} 条记录", |
||||
|
deviceCode, result.Data?.Items?.Count ?? 0); |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,152 @@ |
|||||
|
using CellularManagement.Domain.Entities.Logging; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using X1.Domain.Models; |
||||
|
using X1.Domain.Transmission; |
||||
|
|
||||
|
namespace X1.WebSocket.Transmission |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 网络协议日志观察者
|
||||
|
/// 负责接收协议日志并将其写入通道
|
||||
|
/// </summary>
|
||||
|
public class NetworkProtocolLogObserver : IProtocolLogObserver |
||||
|
{ |
||||
|
private readonly ILogger<NetworkProtocolLogObserver> _logger; |
||||
|
private readonly IProtocolChannelManager _channelManager; |
||||
|
|
||||
|
public NetworkProtocolLogObserver(ILogger<NetworkProtocolLogObserver> logger, IProtocolChannelManager channelManager) |
||||
|
{ |
||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
||||
|
_channelManager = channelManager ?? throw new ArgumentNullException(nameof(channelManager)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 处理接收到的协议日志
|
||||
|
/// </summary>
|
||||
|
/// <param name="logDetails">协议日志集合</param>
|
||||
|
/// <returns>处理任务</returns>
|
||||
|
public async Task OnProtocolLogsReceived(IEnumerable<MessageTransferProtocolLog> logDetails) |
||||
|
{ |
||||
|
// 参数验证
|
||||
|
if (logDetails == null) |
||||
|
{ |
||||
|
_logger.LogWarning("接收到空的协议日志集合"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var logList = logDetails.ToList(); |
||||
|
if (!logList.Any()) |
||||
|
{ |
||||
|
_logger.LogDebug("接收到空的协议日志集合"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_logger.LogInformation("开始处理协议日志,数量:{Count}", logList.Count); |
||||
|
|
||||
|
// 过滤和验证日志数据
|
||||
|
var validLogs = FilterValidLogs(logList); |
||||
|
|
||||
|
if (!validLogs.Any()) |
||||
|
{ |
||||
|
_logger.LogWarning("没有有效的协议日志需要处理"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 转换为ProtocolLog
|
||||
|
var protocolLogs = ConvertToProtocolLogs(validLogs); |
||||
|
|
||||
|
// 写入通道
|
||||
|
var writeResult = await _channelManager.WriteToChannelAsync(protocolLogs, CancellationToken.None); |
||||
|
|
||||
|
if (writeResult) |
||||
|
{ |
||||
|
_logger.LogDebug("成功写入协议日志到通道,数量:{Count}", protocolLogs.Count()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_logger.LogWarning("写入协议日志到通道失败,数量:{Count}", protocolLogs.Count()); |
||||
|
} |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
_logger.LogInformation("协议日志处理被取消"); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "处理协议日志时发生未预期的错误,日志数量:{Count}", logList.Count); |
||||
|
// 不重新抛出异常,避免影响其他操作
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 过滤有效的日志数据
|
||||
|
/// </summary>
|
||||
|
/// <param name="logs">原始日志集合</param>
|
||||
|
/// <returns>有效的日志集合</returns>
|
||||
|
private IEnumerable<MessageTransferProtocolLog> FilterValidLogs(IEnumerable<MessageTransferProtocolLog> logs) |
||||
|
{ |
||||
|
return logs.Where(log => |
||||
|
{ |
||||
|
// 检查必需字段
|
||||
|
if (string.IsNullOrWhiteSpace(log.RuntimeCode)) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:运行时代码为空,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(log.DeviceCode)) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:设备代码为空,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 检查时间戳
|
||||
|
if (log.Timestamp <= 0) |
||||
|
{ |
||||
|
_logger.LogDebug("跳过无效日志:时间戳无效,ID:{Id}", log.Id); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 转换为ProtocolLog实体
|
||||
|
/// </summary>
|
||||
|
/// <param name="logs">MessageTransferProtocolLog集合</param>
|
||||
|
/// <returns>ProtocolLog集合</returns>
|
||||
|
private IEnumerable<ProtocolLog> ConvertToProtocolLogs(IEnumerable<MessageTransferProtocolLog> logs) |
||||
|
{ |
||||
|
return logs.Select(log => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return ProtocolLog.Create( |
||||
|
messageId: log.Id, |
||||
|
layerType: log.LayerType, |
||||
|
direction: log.Direction, |
||||
|
timeMs: log.TimeMs, |
||||
|
timestamp: log.Timestamp, |
||||
|
deviceCode: log.DeviceCode ?? string.Empty, |
||||
|
runtimeCode: log.RuntimeCode, |
||||
|
messageDetailJson: log.MessageDetailJson, |
||||
|
cellID: log.CellID, |
||||
|
imsi: log.IMSI, |
||||
|
ueid: log.UEID, |
||||
|
plmn: log.PLMN, |
||||
|
info: log.Info, |
||||
|
message: log.Message |
||||
|
); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "转换协议日志失败,ID:{Id}", log.Id); |
||||
|
return null; |
||||
|
} |
||||
|
}).Where(log => log != null)!; |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue