Browse Source

查询sql 慢的问题 修复

feature/x1-web-request
root 10 hours ago
parent
commit
9901dfd1fd
  1. 18
      src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdQuery.cs
  2. 75
      src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdQueryHandler.cs
  3. 32
      src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdResponse.cs
  4. 31
      src/X1.Application/Features/ProtocolLogs/Queries/GetProtocolLogsByDevice/GetProtocolLogsByDeviceQueryHandler.cs
  5. 96
      src/X1.Application/Features/ProtocolLogs/Queries/GetProtocolLogsByDevice/GetProtocolLogsByDeviceResponse.cs
  6. 85
      src/X1.Domain/Models/ProtocolLogListDto.cs
  7. 16
      src/X1.Domain/Repositories/Logging/IProtocolLogRepository.cs
  8. 74
      src/X1.Infrastructure/Repositories/Logging/ProtocolLogRepository.cs
  9. 32
      src/X1.Presentation/Controllers/ProtocolLogsController.cs
  10. 176
      src/X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx
  11. 16
      src/X1.WebUI/src/services/protocolLogsService.ts

18
src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdQuery.cs

@ -0,0 +1,18 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetMessageDetailJsonById;
/// <summary>
/// 根据协议日志ID获取消息详情JSON查询
/// </summary>
public class GetMessageDetailJsonByIdQuery : IRequest<OperationResult<GetMessageDetailJsonByIdResponse>>
{
/// <summary>
/// 协议日志ID
/// </summary>
[Required(ErrorMessage = "协议日志ID不能为空")]
[MaxLength(50, ErrorMessage = "协议日志ID不能超过50个字符")]
public string Id { get; set; } = string.Empty;
}

75
src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdQueryHandler.cs

@ -0,0 +1,75 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.Logging;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetMessageDetailJsonById;
/// <summary>
/// 根据协议日志ID获取消息详情JSON查询处理器
/// </summary>
public class GetMessageDetailJsonByIdQueryHandler : IRequestHandler<GetMessageDetailJsonByIdQuery, OperationResult<GetMessageDetailJsonByIdResponse>>
{
private readonly IProtocolLogRepository _protocolLogRepository;
private readonly ILogger<GetMessageDetailJsonByIdQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetMessageDetailJsonByIdQueryHandler(
IProtocolLogRepository protocolLogRepository,
ILogger<GetMessageDetailJsonByIdQueryHandler> logger)
{
_protocolLogRepository = protocolLogRepository;
_logger = logger;
}
/// <summary>
/// 处理根据协议日志ID获取消息详情JSON查询
/// </summary>
public async Task<OperationResult<GetMessageDetailJsonByIdResponse>> Handle(GetMessageDetailJsonByIdQuery 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<GetMessageDetailJsonByIdResponse>.CreateFailure(errorMessages);
}
_logger.LogInformation("开始获取协议日志ID {Id} 的消息详情JSON", request.Id);
// 调用仓储方法获取MessageDetailJson
var messageDetailJson = await _protocolLogRepository.GetMessageDetailJsonByIdAsync(request.Id, cancellationToken);
// 构建响应
var response = new GetMessageDetailJsonByIdResponse
{
Id = request.Id,
MessageDetailJson = messageDetailJson,
Found = !string.IsNullOrEmpty(messageDetailJson)
};
if (response.Found)
{
_logger.LogInformation("成功获取协议日志ID {Id} 的消息详情JSON", request.Id);
}
else
{
_logger.LogWarning("未找到协议日志ID {Id} 的消息详情JSON", request.Id);
}
return OperationResult<GetMessageDetailJsonByIdResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取协议日志ID {Id} 的消息详情JSON时发生错误", request.Id);
return OperationResult<GetMessageDetailJsonByIdResponse>.CreateFailure($"获取消息详情JSON时发生错误: {ex.Message}");
}
}
}

32
src/X1.Application/Features/ProtocolLogs/Queries/GetMessageDetailJsonById/GetMessageDetailJsonByIdResponse.cs

@ -0,0 +1,32 @@
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetMessageDetailJsonById;
/// <summary>
/// 根据协议日志ID获取消息详情JSON响应
/// </summary>
public class GetMessageDetailJsonByIdResponse
{
/// <summary>
/// 协议日志ID
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// 消息详情JSON内容
/// </summary>
public string? MessageDetailJson { get; set; }
/// <summary>
/// 是否找到记录
/// </summary>
public bool Found { get; set; }
///// <summary>
///// 消息详情集合(用于业务逻辑)
///// </summary>
//public IEnumerable<string>? MessageDetail
//{
// get => !string.IsNullOrEmpty(MessageDetailJson)
// ? System.Text.Json.JsonSerializer.Deserialize<IEnumerable<string>>(MessageDetailJson)
// : null;
//}
}

31
src/X1.Application/Features/ProtocolLogs/Queries/GetProtocolLogsByDevice/GetProtocolLogsByDeviceQueryHandler.cs

@ -5,6 +5,7 @@ using CellularManagement.Domain.Entities.Logging;
using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories.Logging;
using CellularManagement.Domain.Repositories.Device;
using CellularManagement.Domain.Models;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@ -52,7 +53,7 @@ public class GetProtocolLogsByDeviceQueryHandler : IRequestHandler<GetProtocolLo
_logger.LogInformation("开始获取设备 {DeviceCode} 的协议日志,运行时状态: {DeviceRuntimeStatus}",
request.DeviceCode, request.DeviceRuntimeStatus);
// 使用 JOIN 高性能查询方法
// 使用高性能查询方法,直接返回ProtocolLogListDto
var protocolLogs = await _protocolLogRepository.GetByDeviceWithFiltersAsync(
request.DeviceCode,
request.RuntimeCodes, // 直接使用请求中的运行时代码数组
@ -63,37 +64,15 @@ public class GetProtocolLogsByDeviceQueryHandler : IRequestHandler<GetProtocolLo
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();
// 构建响应
// 构建响应 - 直接使用Repository返回的ProtocolLogListDto
var response = new GetProtocolLogsByDeviceResponse
{
DeviceCode = request.DeviceCode,
Items = protocolLogDtos.ToList()
Items = protocolLogs.ToList()
};
_logger.LogInformation("成功获取设备 {DeviceCode} 的协议日志,共 {Count} 条记录",
request.DeviceCode, protocolLogDtos.Count);
request.DeviceCode, protocolLogs.Count());
return OperationResult<GetProtocolLogsByDeviceResponse>.CreateSuccess(response);
}

96
src/X1.Application/Features/ProtocolLogs/Queries/GetProtocolLogsByDevice/GetProtocolLogsByDeviceResponse.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using CellularManagement.Domain.Models;
namespace CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice;
@ -17,96 +18,5 @@ public class GetProtocolLogsByDeviceResponse
/// <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; }
}
public List<ProtocolLogListDto> Items { get; set; } = new();
}

85
src/X1.Domain/Models/ProtocolLogListDto.cs

@ -0,0 +1,85 @@
namespace CellularManagement.Domain.Models;
/// <summary>
/// 协议日志列表DTO(用于高性能查询,不包含MessageDetailJson字段)
/// </summary>
public class ProtocolLogListDto
{
/// <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>
/// 小区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 TimeSpan Time
{
get => TimeSpan.FromMilliseconds(TimeMs);
}
}

16
src/X1.Domain/Repositories/Logging/IProtocolLogRepository.cs

@ -1,5 +1,6 @@
using CellularManagement.Domain.Entities.Logging;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Models;
using X1.Domain.Entities.Logging;
namespace CellularManagement.Domain.Repositories.Logging;
@ -51,8 +52,10 @@ public interface IProtocolLogRepository : IBaseRepository<ProtocolLog>
/// <returns>协议日志列表</returns>
Task<IEnumerable<ProtocolLog>> GetByLayerTypeAsync(ProtocolLayer layerType, CancellationToken cancellationToken = default);
/// <summary>
/// 根据设备代码和运行时状态获取协议日志(高性能查询)
/// 根据设备代码和运行时状态获取协议日志(高性能查询,不包含MessageDetailJson
/// </summary>
/// <param name="deviceCode">设备代码</param>
/// <param name="runtimeCodes">运行时代码集合</param>
@ -62,8 +65,8 @@ public interface IProtocolLogRepository : IBaseRepository<ProtocolLog>
/// <param name="runtimeStatuses">运行时状态过滤(可选,支持多个状态)</param>
/// <param name="orderByDescending">是否按时间戳降序排序</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>协议日志列表</returns>
Task<IEnumerable<ProtocolLog>> GetByDeviceWithFiltersAsync(
/// <returns>协议日志列表DTO</returns>
Task<IEnumerable<ProtocolLogListDto>> GetByDeviceWithFiltersAsync(
string? deviceCode,
IEnumerable<string>? runtimeCodes = null,
long? startTimestamp = null,
@ -73,5 +76,12 @@ public interface IProtocolLogRepository : IBaseRepository<ProtocolLog>
bool orderByDescending = true,
CancellationToken cancellationToken = default);
/// <summary>
/// 根据协议日志ID获取MessageDetailJson
/// </summary>
/// <param name="id">协议日志ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>MessageDetailJson内容</returns>
Task<string?> GetMessageDetailJsonByIdAsync(string id, CancellationToken cancellationToken = default);
}

74
src/X1.Infrastructure/Repositories/Logging/ProtocolLogRepository.cs

@ -5,6 +5,7 @@ using CellularManagement.Domain.Entities.Device;
using CellularManagement.Domain.Repositories.Logging;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Infrastructure.Repositories.Base;
using CellularManagement.Domain.Models;
using System.Linq;
using X1.Domain.Entities.Logging;
@ -149,8 +150,10 @@ public class ProtocolLogRepository : BaseRepository<ProtocolLog>, IProtocolLogRe
}
}
/// <summary>
/// 根据设备代码和运行时状态获取协议日志(高性能查询)
/// 根据设备代码和运行时状态获取协议日志(高性能查询,不包含MessageDetailJson
/// </summary>
/// <param name="deviceCode">设备代码</param>
/// <param name="runtimeCodes">运行时代码集合</param>
@ -160,8 +163,8 @@ public class ProtocolLogRepository : BaseRepository<ProtocolLog>, IProtocolLogRe
/// <param name="runtimeStatuses">运行时状态过滤(可选,支持多个状态)</param>
/// <param name="orderByDescending">是否按时间戳降序排序</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>协议日志列表</returns>
public async Task<IEnumerable<ProtocolLog>> GetByDeviceWithFiltersAsync(
/// <returns>协议日志列表DTO</returns>
public async Task<IEnumerable<ProtocolLogListDto>> GetByDeviceWithFiltersAsync(
string? deviceCode,
IEnumerable<string>? runtimeCodes = null,
long? startTimestamp = null,
@ -173,9 +176,23 @@ public class ProtocolLogRepository : BaseRepository<ProtocolLog>, IProtocolLogRe
{
try
{
// 构建 SQL 查询
// 构建 SQL 查询 - 排除 MessageDetailJson 字段以提升性能
var sql = @"
SELECT pl.*
SELECT
pl.""Id"",
pl.""MessageId"",
pl.""LayerType"",
pl.""CellID"",
pl.""IMSI"",
pl.""Direction"",
pl.""UEID"",
pl.""PLMN"",
pl.""TimeMs"",
pl.""Timestamp"",
pl.""Info"",
pl.""Message"",
pl.""DeviceCode"",
pl.""RuntimeCode""
FROM ""tb_protocol_logs"" pl
INNER JOIN ""tb_cellular_device_runtimes"" cdr
ON pl.""DeviceCode"" = cdr.""DeviceCode""";
@ -238,16 +255,53 @@ public class ProtocolLogRepository : BaseRepository<ProtocolLog>, IProtocolLogRe
}
}
// 添加排序
sql += orderByDescending ? " ORDER BY pl.\"Timestamp\" DESC" : " ORDER BY pl.\"Timestamp\" ASC";
// 添加排序 - 只按Timestamp排序
//if (orderByDescending)
//{
// sql += " ORDER BY pl.\"Timestamp\" DESC";
//}
//else
//{
// sql += " ORDER BY pl.\"Timestamp\" ASC";
//}
// 执行 SQL 查询
var logs = await QueryRepository.ExecuteSqlQueryAsync<ProtocolLog>(sql, parameters.ToArray(), cancellationToken);
// 执行 SQL 查询,直接映射到ProtocolLogListDto
var logs = await QueryRepository.ExecuteSqlQueryAsync<ProtocolLogListDto>(sql, parameters.ToArray(), cancellationToken);
return logs;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取设备代码 {DeviceCode} 的协议日志时发生错误", deviceCode);
_logger.LogError(ex, "获取设备代码 {DeviceCode} 的协议日志列表时发生错误", deviceCode);
throw;
}
}
/// <summary>
/// 根据协议日志ID获取MessageDetailJson
/// </summary>
/// <param name="id">协议日志ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>MessageDetailJson内容</returns>
public async Task<string?> GetMessageDetailJsonByIdAsync(string id, CancellationToken cancellationToken = default)
{
try
{
// 使用SQL查询,只获取MessageDetailJson字段,性能最高
var sql = @"
SELECT pl.""MessageDetailJson""
FROM ""tb_protocol_logs"" pl
WHERE pl.""Id"" = {0}";
var parameters = new[] { id };
// 执行SQL查询,只返回MessageDetailJson字段
var result = await QueryRepository.ExecuteSqlQueryAsync<string>(sql, parameters, cancellationToken);
return result.FirstOrDefault();
}
catch (Exception ex)
{
_logger.LogError(ex, "获取协议日志ID {Id} 的MessageDetailJson时发生错误", id);
throw;
}
}

32
src/X1.Presentation/Controllers/ProtocolLogsController.cs

@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using CellularManagement.Application.Features.ProtocolLogs.Queries.GetProtocolLogsByDevice;
using CellularManagement.Application.Features.ProtocolLogs.Queries.GetMessageDetailJsonById;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Entities.Device;
using CellularManagement.Presentation.Abstractions;
@ -55,4 +56,35 @@ public class ProtocolLogsController : ApiController
result.Data?.Items?.Count ?? 0);
return result;
}
/// <summary>
/// 根据协议日志ID获取消息详情JSON
/// </summary>
/// <param name="id">协议日志ID</param>
/// <returns>消息详情JSON内容</returns>
[HttpGet("message-detail/{id}")]
public async Task<OperationResult<GetMessageDetailJsonByIdResponse>> GetMessageDetailJsonById(string id)
{
_logger.LogInformation("开始获取协议日志ID {Id} 的消息详情JSON", id);
var query = new GetMessageDetailJsonByIdQuery { Id = id };
var result = await mediator.Send(query);
if (!result.IsSuccess)
{
_logger.LogWarning("获取协议日志ID {Id} 的消息详情JSON失败: {Message}", id, result.ErrorMessages);
return result;
}
if (result.Data?.Found == true)
{
_logger.LogInformation("成功获取协议日志ID {Id} 的消息详情JSON", id);
}
else
{
_logger.LogWarning("未找到协议日志ID {Id} 的消息详情JSON", id);
}
return result;
}
}

176
src/X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx

@ -1,14 +1,15 @@
import React, { useState } from 'react';
import { ProtocolLogDto } from '@/services/protocolLogsService';
import { ProtocolLogDto, protocolLogsService } from '@/services/protocolLogsService';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Drawer, DrawerContent, DrawerHeader } from '@/components/ui/drawer';
import { Eye, X } from 'lucide-react';
import { Eye, X, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { DensityType } from '@/components/ui/TableToolbar';
import { getProtocolLayerLabel } from '@/constants/protocolLayerOptions';
import ConfigContentViewer from '@/components/ui/ConfigContentViewer';
import { useToast } from '@/components/ui/use-toast';
interface ProtocolLogsTableProps {
protocolLogs: ProtocolLogDto[];
@ -25,12 +26,42 @@ export default function ProtocolLogsTable({
}: ProtocolLogsTableProps) {
const [selectedMessageDetail, setSelectedMessageDetail] = useState<string>('');
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [loadingMessageDetail, setLoadingMessageDetail] = useState(false);
const { toast } = useToast();
// 密度样式映射
// 密度样式映射 - 更紧凑的样式
const densityStyles = {
relaxed: 'py-3',
compact: 'py-1',
default: 'py-2',
relaxed: 'py-2 px-2',
compact: 'py-1 px-1',
default: 'py-1.5 px-2',
};
// 列宽度配置 - 为Message列分配更多空间
const getColumnWidth = (columnKey: string) => {
switch (columnKey) {
case 'layerType':
return 'w-16'; // 固定宽度
case 'time':
return 'w-24'; // 时间列
case 'plmn':
return 'w-20'; // PLMN列
case 'info':
return 'w-24'; // Info列
case 'ueid':
return 'w-16'; // UEID列
case 'imsi':
return 'w-28'; // IMSI列
case 'cellID':
return 'w-16'; // CellID列
case 'direction':
return 'w-16'; // 方向列
case 'message':
return 'flex-1 min-w-0'; // Message列占用剩余空间
case 'MessageDetailJson':
return 'w-12'; // 详情按钮列
default:
return 'w-auto';
}
};
// 格式化时间戳
@ -69,94 +100,108 @@ export default function ProtocolLogsTable({
// 查看消息详情
const handleViewMessageDetail = (messageDetailJson: string) => {
// 尝试格式化 JSON 数据
let formattedContent = messageDetailJson;
try {
// 如果是 JSON 字符串,尝试解析并格式化
if (messageDetailJson && (messageDetailJson.startsWith('[') || messageDetailJson.startsWith('{'))) {
const parsed = JSON.parse(messageDetailJson);
formattedContent = JSON.stringify(parsed, null, 2);
const handleViewMessageDetail = async (log: ProtocolLogDto) => {
// 如果已经有消息详情,直接显示
if (log.messageDetailJson) {
let formattedContent = log.messageDetailJson;
try {
// 如果是 JSON 字符串,尝试解析并格式化
if (log.messageDetailJson && (log.messageDetailJson.startsWith('[') || log.messageDetailJson.startsWith('{'))) {
const parsed = JSON.parse(log.messageDetailJson);
formattedContent = JSON.stringify(parsed, null, 2);
}
} catch (error) {
// 如果解析失败,保持原始内容
console.log('messageDetailJson 不是有效的 JSON 格式,使用原始内容');
}
} catch (error) {
// 如果解析失败,保持原始内容
console.log('messageDetailJson 不是有效的 JSON 格式,使用原始内容');
setSelectedMessageDetail(formattedContent);
setIsDrawerOpen(true);
return;
}
setSelectedMessageDetail(formattedContent);
setIsDrawerOpen(true);
};
// 复制消息详情
const handleCopyMessageDetail = async () => {
// 如果没有消息详情,从API获取
setLoadingMessageDetail(true);
try {
await navigator.clipboard.writeText(selectedMessageDetail);
// 可以添加一个 toast 提示
const result = await protocolLogsService.getMessageDetailJsonById(log.id);
if (result.isSuccess && result.data?.found) {
let formattedContent = result.data.messageDetailJson || '';
try {
if (formattedContent && (formattedContent.startsWith('[') || formattedContent.startsWith('{'))) {
const parsed = JSON.parse(formattedContent);
formattedContent = JSON.stringify(parsed, null, 2);
}
} catch (error) {
console.log('messageDetailJson 不是有效的 JSON 格式,使用原始内容');
}
setSelectedMessageDetail(formattedContent);
setIsDrawerOpen(true);
} else {
toast({
title: "获取失败",
description: result.errorMessages?.join(', ') || "未找到消息详情",
variant: "destructive",
});
}
} catch (error) {
console.error('复制失败:', error);
toast({
title: "获取失败",
description: "获取消息详情时发生错误",
variant: "destructive",
});
} finally {
setLoadingMessageDetail(false);
}
};
// 下载消息详情
const handleDownloadMessageDetail = () => {
const blob = new Blob([selectedMessageDetail], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `message-detail-${Date.now()}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// 渲染单元格内容
const renderCell = (log: ProtocolLogDto, columnKey: string) => {
switch (columnKey) {
case 'layerType':
return (
<div className="text-sm">
<div className="text-xs font-medium">
{getProtocolLayerLabel(log.layerType)}
</div>
);
case 'time':
return (
<div className="text-sm text-muted-foreground">
<div className="text-xs text-muted-foreground font-mono">
{log.time ? log.time.substring(0, 12) : '-'}
</div>
);
case 'plmn':
return (
<div className="text-sm font-mono">
<div className="text-xs font-mono">
{(log as any).plmn || log.PLMN || '-'}
</div>
);
case 'info':
return (
<div className="text-sm max-w-xs truncate" title={log.info}>
<div className="text-xs max-w-full truncate" title={log.info}>
{log.info || '-'}
</div>
);
case 'ueid':
return (
<div className="text-sm text-muted-foreground">
<div className="text-xs text-muted-foreground">
{(log as any).ueid || log.UEID || '-'}
</div>
);
case 'imsi':
return (
<div className="text-sm font-mono">
<div className="text-xs font-mono">
{(log as any).imsi || log.IMSI || '-'}
</div>
);
case 'cellID':
return (
<div className="text-sm text-muted-foreground">
<div className="text-xs text-muted-foreground">
{(log as any).cellID || log.CellID || '-'}
</div>
);
@ -167,7 +212,7 @@ export default function ProtocolLogsTable({
return (
<Badge
variant="outline"
className={cn(directionColor)}
className={cn(directionColor, "text-xs px-1 py-0.5")}
>
{directionDesc}
</Badge>
@ -175,7 +220,7 @@ export default function ProtocolLogsTable({
case 'message':
return (
<div className="text-sm max-w-xs truncate" title={log.message}>
<div className="text-xs break-words whitespace-normal text-left" title={log.message}>
{log.message || '-'}
</div>
);
@ -186,12 +231,16 @@ export default function ProtocolLogsTable({
<Button
variant="ghost"
size="sm"
onClick={() => handleViewMessageDetail(log.messageDetailJson || '')}
disabled={!log.messageDetailJson}
className="h-8 w-8 p-0 hover:bg-accent"
onClick={() => handleViewMessageDetail(log)}
disabled={loadingMessageDetail}
className="h-6 w-6 p-0 hover:bg-accent"
title="查看详情"
>
<Eye className="h-4 w-4 text-muted-foreground hover:text-foreground" />
{loadingMessageDetail ? (
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
) : (
<Eye className="h-3 w-3 text-muted-foreground hover:text-foreground" />
)}
</Button>
</div>
);
@ -210,11 +259,18 @@ export default function ProtocolLogsTable({
<div className="overflow-x-auto">
{/* 固定的表头 */}
<div className="bg-background border-b shadow-sm">
<Table className="w-full table-fixed">
<Table className="w-full">
<TableHeader>
<TableRow>
<TableRow className="border-b-0">
{visibleColumns.map((column) => (
<TableHead key={column.key} className={cn("text-center whitespace-nowrap bg-background", densityStyles[density])} style={{ width: `${100 / visibleColumns.length}%` }}>
<TableHead
key={column.key}
className={cn(
"text-center whitespace-nowrap bg-background text-xs font-medium",
densityStyles[density],
getColumnWidth(column.key)
)}
>
{column.title}
</TableHead>
))}
@ -225,7 +281,7 @@ export default function ProtocolLogsTable({
{/* 可滚动的表体 */}
<div className="max-h-[600px] overflow-auto">
<Table className="w-full table-fixed">
<Table className="w-full">
<TableBody>
{loading ? (
<TableRow>
@ -246,9 +302,17 @@ export default function ProtocolLogsTable({
</TableRow>
) : (
protocolLogs.map((log, index) => (
<TableRow key={`${log.id}-${index}`}>
<TableRow key={`${log.id}-${index}`} className="hover:bg-muted/50">
{visibleColumns.map((column) => (
<TableCell key={column.key} className={cn("text-center whitespace-nowrap", densityStyles[density])} style={{ width: `${100 / visibleColumns.length}%` }}>
<TableCell
key={column.key}
className={cn(
"text-center whitespace-nowrap border-b",
densityStyles[density],
getColumnWidth(column.key),
column.key === 'message' ? 'text-left' : 'text-center'
)}
>
{renderCell(log, column.key)}
</TableCell>
))}

16
src/X1.WebUI/src/services/protocolLogsService.ts

@ -49,6 +49,13 @@ export interface GetProtocolLogsResponse {
items: ProtocolLogDto[];
}
// 获取消息详情JSON响应接口
export interface GetMessageDetailJsonResponse {
id: string;
messageDetailJson?: string;
found: boolean;
}
class ProtocolLogsService {
private readonly baseUrl = API_PATHS.PROTOCOL_LOGS;
@ -139,6 +146,15 @@ class ProtocolLogsService {
...options
});
}
/**
* ID获取消息详情JSON
* @param id ID
* @returns JSON内容
*/
async getMessageDetailJsonById(id: string): Promise<OperationResult<GetMessageDetailJsonResponse>> {
return httpClient.get<GetMessageDetailJsonResponse>(`${this.baseUrl}/message-detail/${id}`);
}
}
export const protocolLogsService = new ProtocolLogsService();
Loading…
Cancel
Save