You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
404 lines
17 KiB
404 lines
17 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using CoreAgent.ProtocolClient.Context;
|
|
using CoreAgent.ProtocolClient.Enums;
|
|
using CoreAgent.ProtocolClient.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace CoreAgent.ProtocolClient.ProtocolEngineCore
|
|
{
|
|
/// <summary>
|
|
/// 日志数据转换器
|
|
/// 负责将BuildProtocolLog转换为TransferProtocolLog,并处理IMSI等关联数据的获取
|
|
/// </summary>
|
|
public class LogDataConverter
|
|
{
|
|
private readonly ProtocolClientContext _context;
|
|
private readonly ILogger _logger;
|
|
|
|
public LogDataConverter(ProtocolClientContext context, ILogger logger)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将BuildProtocolLog列表转换为TransferProtocolLog列表
|
|
/// </summary>
|
|
/// <param name="protocolLogs">协议日志列表</param>
|
|
/// <returns>协议日志详情列表</returns>
|
|
public List<TransferProtocolLog> ConvertToProtocolLogDetails(IEnumerable<BuildProtocolLog> protocolLogs)
|
|
{
|
|
var result = new List<TransferProtocolLog>();
|
|
var tmsiMatches = _context.UeIdentifier.GenerateTmsiMatches();
|
|
var srTmsiMappings = _context.UeIdentifier.GenerateSrTmsiMappings(tmsiMatches);
|
|
foreach (var log in protocolLogs)
|
|
{
|
|
try
|
|
{
|
|
var detail = ConvertSingleLog(log, tmsiMatches, srTmsiMappings);
|
|
if (detail != null)
|
|
{
|
|
result.Add(detail);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"转换日志详情失败,Layer: {log.Layer}, Message: {log.Message}");
|
|
}
|
|
}
|
|
|
|
// 更新 cellid 为 null 的记录
|
|
UpdateNullCellIds(result);
|
|
|
|
// 统计IMSI和PLMN为null的数量
|
|
LogNullStatistics(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新 cellid 为 null 的记录
|
|
/// 通过先根据 ueid 不为空分组,然后检查 cellid 为 null 的 ueid 与分组 ueid 匹配修改
|
|
/// </summary>
|
|
/// <param name="details">协议日志详情列表</param>
|
|
private void UpdateNullCellIds(List<TransferProtocolLog> details)
|
|
{
|
|
if (details == null || !details.Any())
|
|
{
|
|
_logger.LogInformation("没有日志详情需要更新 cellid");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 1. 先根据 ueid 不为空分组,获取每个 UE ID 对应的有效 cellid
|
|
var ueIdToCellIdMapping = details
|
|
.Where(d => d.UEID.HasValue && d.CellID.HasValue)
|
|
.GroupBy(d => d.UEID.Value)
|
|
.ToDictionary(
|
|
group => group.Key,
|
|
group => group.Select(d => d.CellID.Value).Distinct().ToList()
|
|
);
|
|
|
|
if (!ueIdToCellIdMapping.Any())
|
|
{
|
|
_logger.LogInformation("没有找到有效的 UE ID 到 Cell ID 映射");
|
|
return;
|
|
}
|
|
|
|
// 2. 检查 cellid 为 null 的记录
|
|
var nullCellIdDetails = details.Where(d => d.UEID.HasValue && !d.CellID.HasValue).ToList();
|
|
|
|
if (!nullCellIdDetails.Any())
|
|
{
|
|
_logger.LogInformation("没有找到 cellid 为 null 的记录");
|
|
return;
|
|
}
|
|
|
|
_logger.LogInformation($"找到 {nullCellIdDetails.Count} 条 cellid 为 null 的记录,开始更新...");
|
|
|
|
// 3. 通过 ueid 匹配来更新这些记录的 cellid
|
|
int updatedCount = 0;
|
|
foreach (var detail in nullCellIdDetails)
|
|
{
|
|
if (detail.UEID.HasValue && ueIdToCellIdMapping.TryGetValue(detail.UEID.Value, out var cellIds))
|
|
{
|
|
// 如果该 UE ID 有多个 cellid,选择第一个(或者可以根据业务逻辑选择最合适的)
|
|
if (cellIds.Any())
|
|
{
|
|
detail.CellID = cellIds.First();
|
|
updatedCount++;
|
|
_logger.LogDebug($"更新 UE ID {detail.UEID} 的 cellid 为 {detail.CellID}");
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation($"成功更新了 {updatedCount} 条记录的 cellid");
|
|
|
|
// 4. 统计更新结果
|
|
var remainingNullCellIds = details.Where(d => d.UEID.HasValue && !d.CellID.HasValue).Count();
|
|
if (remainingNullCellIds > 0)
|
|
{
|
|
_logger.LogWarning($"仍有 {remainingNullCellIds} 条记录的 cellid 为 null,这些 UE ID 可能没有对应的 cellid 信息");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "更新 cellid 为 null 的记录时发生异常");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 转换单个BuildProtocolLog为TransferProtocolLog
|
|
/// </summary>
|
|
/// <param name="log">协议日志</param>
|
|
/// <param name="tmsiMatches">TMSI匹配结果列表</param>
|
|
/// <param name="srTmsiMappings">Service Request TMSI映射列表</param>
|
|
/// <returns>协议日志详情</returns>
|
|
private TransferProtocolLog? ConvertSingleLog(BuildProtocolLog log, List<TmsiMatchResult> tmsiMatches, List<SrTmsiMapping> srTmsiMappings)
|
|
{
|
|
if (log == null)
|
|
return null;
|
|
|
|
var detail = new TransferProtocolLog
|
|
{
|
|
Id=log.Id,
|
|
LayerType = GetProtocolLayer(log.Layer),
|
|
Direction = log.Direction,
|
|
UEID = log.UeId,
|
|
CellID = log.Cell,
|
|
Timestamp = log.Timestamp,
|
|
TimeMs = log.Timestamp,
|
|
Message = log.Message,
|
|
Info = _context.UeIdentifier.IdToString(log.Info ?? -1),
|
|
MessageDetailJson = JsonConvert.SerializeObject(log.Data)
|
|
};
|
|
|
|
// 获取IMSI信息
|
|
detail.IMSI = GetImsiFromUeList(log.UeId, tmsiMatches, srTmsiMappings);
|
|
|
|
// 获取PLMN信息
|
|
detail.PLMN = GetPlmnFromPlmnToUeIdMapping(log.UeId, srTmsiMappings);
|
|
|
|
return detail;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据UE ID获取IMSI信息
|
|
/// 使用TmsiMatchProcessor从UE链中获取IMSI,优先从根节点获取
|
|
/// </summary>
|
|
/// <param name="ueId">UE ID</param>
|
|
/// <param name="tmsiMatches">TMSI匹配结果列表</param>
|
|
/// <param name="srTmsiMappings">Service Request TMSI映射列表</param>
|
|
/// <returns>IMSI字符串,如果未找到则返回null</returns>
|
|
private string? GetImsiFromUeList(int? ueId, List<TmsiMatchResult> tmsiMatches, List<SrTmsiMapping> srTmsiMappings)
|
|
{
|
|
if (!ueId.HasValue)
|
|
return null;
|
|
|
|
// 1. 优先从TMSI匹配结果中查找IMSI
|
|
// 查找包含当前UE ID的匹配结果
|
|
var matchingResult = tmsiMatches.FirstOrDefault(match =>
|
|
match.RequestUeId == ueId.Value || match.ReceiveUeId == ueId.Value);
|
|
|
|
if (matchingResult != null && !string.IsNullOrEmpty(matchingResult.Imsi))
|
|
{
|
|
return matchingResult.Imsi;
|
|
}
|
|
|
|
// 2. 从Service Request TMSI映射中查找IMSI
|
|
var srTmsiMatch = srTmsiMappings.FirstOrDefault(mapping => mapping.UeId == ueId.Value);
|
|
if (srTmsiMatch != null && !string.IsNullOrEmpty(srTmsiMatch.Imsi))
|
|
{
|
|
return srTmsiMatch.Imsi;
|
|
}
|
|
|
|
// 3. 如果TMSI匹配中没有找到,使用TmsiMatchProcessor从UE链中查找
|
|
var processor = new TmsiMatchProcessor(
|
|
_context.UeIdentifier.TmsiToUeId,
|
|
_context.UeIdentifier.RequestTmsiToUeId,
|
|
_context.UeIdentifier.ImsiToUeId);
|
|
|
|
// 获取UE链详细信息
|
|
var ueChains = processor.GetUeChainDetails();
|
|
|
|
// 查找包含当前UE ID的链
|
|
var containingChain = ueChains.FirstOrDefault(chain => chain.UeIds.Contains(ueId.Value));
|
|
if (containingChain != null && !string.IsNullOrEmpty(containingChain.Imsi))
|
|
{
|
|
return containingChain.Imsi;
|
|
}
|
|
|
|
// 4. 如果UE链中也没有找到,回退到原有的查找逻辑
|
|
return GetImsiFromLegacySources(ueId.Value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从传统数据源获取IMSI(回退方法)
|
|
/// 查找顺序:1.直接查找UeList 2.从Caps中查找 3.从ImsiToUeId映射表查找
|
|
/// </summary>
|
|
/// <param name="ueId">UE ID</param>
|
|
/// <returns>IMSI字符串,如果未找到则返回null</returns>
|
|
private string? GetImsiFromLegacySources(int ueId)
|
|
{
|
|
// 1. 直接从UeList中查找UE信息
|
|
if (_context.UeIdentifier.UeList?.TryGetValue(ueId, out var ueInfo) == true)
|
|
{
|
|
if (!string.IsNullOrEmpty(ueInfo?.Imsi))
|
|
{
|
|
return ueInfo.Imsi;
|
|
}
|
|
}
|
|
|
|
// 2. 从其他UE的Caps中查找UE ID
|
|
if (_context.UeIdentifier.UeList != null)
|
|
{
|
|
var imsiFromCaps = _context.UeIdentifier.UeList.Values
|
|
.Where(ueInfoItem => ueInfoItem?.Caps?.UeId == ueId)
|
|
.Select(ueInfoItem => ueInfoItem.Imsi)
|
|
.FirstOrDefault(imsi => !string.IsNullOrEmpty(imsi));
|
|
|
|
if (imsiFromCaps != null)
|
|
{
|
|
return imsiFromCaps;
|
|
}
|
|
}
|
|
|
|
// 3. 从ImsiToUeId映射表中查找
|
|
if (_context.UeIdentifier.ImsiToUeId != null)
|
|
{
|
|
var imsiFromMapping = _context.UeIdentifier.ImsiToUeId
|
|
.Where(mapping => mapping.Value?.Contains(ueId) == true)
|
|
.Select(mapping => mapping.Key)
|
|
.FirstOrDefault(imsi => !string.IsNullOrEmpty(imsi));
|
|
|
|
if (imsiFromMapping != null)
|
|
{
|
|
return imsiFromMapping;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据协议层字符串获取ProtocolLayer枚举
|
|
/// </summary>
|
|
/// <param name="layer">协议层字符串</param>
|
|
/// <returns>ProtocolLayer枚举值</returns>
|
|
private ProtocolLayer GetProtocolLayer(string layer)
|
|
{
|
|
return layer?.ToUpperInvariant() switch
|
|
{
|
|
"RRC" => ProtocolLayer.RRC,
|
|
"NAS" => ProtocolLayer.NAS,
|
|
"PHY" => ProtocolLayer.PHY,
|
|
"MAC" => ProtocolLayer.MAC,
|
|
"RLC" => ProtocolLayer.RLC,
|
|
"PDCP" => ProtocolLayer.PDCP,
|
|
"SIP" => ProtocolLayer.SIP,
|
|
"IMS" => ProtocolLayer.IMS,
|
|
_ => ProtocolLayer.NONE
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据UE ID从UeIdentifier.PlmnToUeId映射表中获取PLMN
|
|
/// 优先使用srTmsiMappings中的RootUeId查找PLMN,提高匹配准确性
|
|
/// </summary>
|
|
/// <param name="ueId">UE ID</param>
|
|
/// <param name="srTmsiMappings">Service Request TMSI映射列表</param>
|
|
/// <returns>PLMN字符串,如果未找到则返回null</returns>
|
|
private string? GetPlmnFromPlmnToUeIdMapping(int? ueId, List<SrTmsiMapping> srTmsiMappings)
|
|
{
|
|
if (!ueId.HasValue)
|
|
return null;
|
|
|
|
// 1. 优先从srTmsiMappings中查找对应的RootUeId,然后根据RootUeId查找PLMN
|
|
var srTmsiMatch = srTmsiMappings.FirstOrDefault(mapping => mapping.UeId == ueId.Value);
|
|
if (srTmsiMatch != null && srTmsiMatch.RootUeId > 0)
|
|
{
|
|
// 检查PlmnToUeId映射表是否存在
|
|
if (_context.UeIdentifier.PlmnToUeId != null)
|
|
{
|
|
// 直接使用RootUeId查找PLMN映射
|
|
var plmnMapping = _context.UeIdentifier.PlmnToUeId
|
|
.FirstOrDefault(mapping => mapping.Value?.Contains(srTmsiMatch.RootUeId) == true);
|
|
|
|
if (!string.IsNullOrEmpty(plmnMapping.Key))
|
|
{
|
|
return plmnMapping.Key;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 如果srTmsiMappings中没有找到或RootUeId为空,回退到原有的查找逻辑
|
|
// 检查PlmnToUeId映射表是否存在
|
|
if (_context.UeIdentifier.PlmnToUeId == null)
|
|
return null;
|
|
|
|
// 直接查找包含指定UE ID的PLMN映射
|
|
var directPlmnMapping = _context.UeIdentifier.PlmnToUeId
|
|
.FirstOrDefault(mapping => mapping.Value?.Contains(ueId.Value) == true);
|
|
|
|
return directPlmnMapping.Key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 统计并打印有问题的日志详情
|
|
/// </summary>
|
|
/// <param name="details">协议日志详情列表</param>
|
|
private void LogNullStatistics(List<TransferProtocolLog> details)
|
|
{
|
|
if (details == null || !details.Any())
|
|
{
|
|
_logger.LogInformation("没有日志详情需要统计");
|
|
return;
|
|
}
|
|
|
|
// 1. UE ID为空的日志
|
|
var ueIdNullLogs = details.Where(d => d.UEID == null).ToList();
|
|
if (ueIdNullLogs.Any())
|
|
{
|
|
_logger.LogWarning($"发现 {ueIdNullLogs.Count} 条UE ID为空的日志:");
|
|
foreach (var log in ueIdNullLogs)
|
|
{
|
|
_logger.LogWarning($" UE ID为空 - Layer: {log.LayerType}, Message: {log.Message}, IMSI: {log.IMSI}, PLMN: {log.PLMN}");
|
|
}
|
|
}
|
|
|
|
// 2. 只有IMSI但没有PLMN的日志
|
|
var onlyImsiLogs = details.Where(d => d.UEID.HasValue && !string.IsNullOrEmpty(d.IMSI) && string.IsNullOrEmpty(d.PLMN)).ToList();
|
|
if (onlyImsiLogs.Any())
|
|
{
|
|
_logger.LogWarning($"发现 {onlyImsiLogs.Count} 条只有IMSI但没有PLMN的日志:");
|
|
foreach (var log in onlyImsiLogs)
|
|
{
|
|
_logger.LogWarning($" 只有IMSI - UE ID: {log.UEID}, Layer: {log.LayerType}, IMSI: {log.IMSI}, PLMN: {log.PLMN}");
|
|
}
|
|
}
|
|
|
|
// 3. 只有PLMN但没有IMSI的日志
|
|
var onlyPlmnLogs = details.Where(d => d.UEID.HasValue && string.IsNullOrEmpty(d.IMSI) && !string.IsNullOrEmpty(d.PLMN)).ToList();
|
|
if (onlyPlmnLogs.Any())
|
|
{
|
|
_logger.LogWarning($"发现 {onlyPlmnLogs.Count} 条只有PLMN但没有IMSI的日志:");
|
|
foreach (var log in onlyPlmnLogs)
|
|
{
|
|
_logger.LogWarning($" 只有PLMN - UE ID: {log.UEID}, Layer: {log.LayerType}, IMSI: {log.IMSI}, PLMN: {log.PLMN}");
|
|
}
|
|
}
|
|
|
|
// 4. 同时有IMSI和PLMN的日志
|
|
var bothLogs = details.Where(d => d.UEID.HasValue && !string.IsNullOrEmpty(d.IMSI) && !string.IsNullOrEmpty(d.PLMN)).ToList();
|
|
if (bothLogs.Any())
|
|
{
|
|
_logger.LogInformation($"发现 {bothLogs.Count} 条同时有IMSI和PLMN的日志:");
|
|
foreach (var log in bothLogs)
|
|
{
|
|
_logger.LogInformation($" 完整信息 - UE ID: {log.UEID}, Layer: {log.LayerType}, IMSI: {log.IMSI}, PLMN: {log.PLMN}");
|
|
}
|
|
}
|
|
|
|
// 5. 两者都为空的日志(UE ID不为空)
|
|
var bothNullLogs = details.Where(d => d.UEID.HasValue && string.IsNullOrEmpty(d.IMSI) && string.IsNullOrEmpty(d.PLMN)).ToList();
|
|
if (bothNullLogs.Any())
|
|
{
|
|
_logger.LogWarning($"发现 {bothNullLogs.Count} 条UE ID不为空但IMSI和PLMN都为空的日志:");
|
|
foreach (var log in bothNullLogs)
|
|
{
|
|
_logger.LogWarning($" 信息缺失 - UE ID: {log.UEID}, Layer: {log.LayerType}, IMSI: {log.IMSI}, PLMN: {log.PLMN}");
|
|
}
|
|
}
|
|
|
|
// 总结统计
|
|
var totalCount = details.Count;
|
|
_logger.LogInformation($"日志详情总结 - 总数: {totalCount}, UE ID为空: {ueIdNullLogs.Count}, 只有IMSI: {onlyImsiLogs.Count}, 只有PLMN: {onlyPlmnLogs.Count}, 完整信息: {bothLogs.Count}, 信息缺失: {bothNullLogs.Count}");
|
|
}
|
|
}
|
|
}
|
|
|