Browse Source

feat: 优化客户端消息页面性能

- 将消息页面的全量轮询改为增量更新,大幅提升前端性能。
- 实现消息数量上限控制,防止DOM节点无限增长导致卡顿。
- 后端API支持按索引获取新消息,减少数据传输量。
- 增加XSS防护,提升安全性。
master
root 1 month ago
parent
commit
6187685707
  1. 35
      LTEMvcApp/Controllers/WebSocketController.cs
  2. 160
      LTEMvcApp/Views/Home/ClientMessages.cshtml

35
LTEMvcApp/Controllers/WebSocketController.cs

@ -247,23 +247,38 @@ namespace LTEMvcApp.Controllers
/// 获取客户端消息队列
/// </summary>
/// <param name="clientName">客户端名称</param>
/// <param name="sentStartIndex">发送消息起始索引</param>
/// <param name="receivedStartIndex">接收消息起始索引</param>
/// <returns>发送和接收的消息队列</returns>
[HttpGet("clients/{clientName}/messages")]
public ActionResult<object> GetClientMessages(string clientName)
public ActionResult<object> GetClientMessages(string clientName, [FromQuery] int sentStartIndex = 0, [FromQuery] int receivedStartIndex = 0)
{
var client = _webSocketManager.GetClientInstance(clientName);
if (client == null)
return NotFound($"客户端 '{clientName}' 不存在或未连接");
var result = new
{
SentMessages = client.SentMessages.ToList(),
ReceivedMessages = client.ReceivedMessages.ToList(),
SentCount = client.SentMessages.Count(),
ReceivedCount = client.ReceivedMessages.Count()
};
// 即使客户端未连接,也可能是在配置页面中查看,所以返回空而不是404
return Ok(new
{
SentMessages = new List<string>(),
ReceivedMessages = new List<string>(),
SentCount = 0,
ReceivedCount = 0
});
}
return Ok(result);
var sentMessages = client.SentMessages.ToList();
var receivedMessages = client.ReceivedMessages.ToList();
var newSent = sentMessages.Skip(sentStartIndex).ToList();
var newReceived = receivedMessages.Skip(receivedStartIndex).ToList();
return Ok(new
{
NewSentMessages = newSent,
NewReceivedMessages = newReceived,
TotalSentCount = sentMessages.Count,
TotalReceivedCount = receivedMessages.Count
});
}
/// <summary>

160
LTEMvcApp/Views/Home/ClientMessages.cshtml

@ -63,94 +63,127 @@
@section Scripts {
<script>
var clientName = '@clientName';
var refreshInterval;
const clientName = '@clientName';
const MAX_MESSAGES = 500; // 每个列表最多显示500条消息
let sentIndex = 0;
let receivedIndex = 0;
let updateInterval;
$(document).ready(function() {
loadMessages();
// 每5秒自动刷新一次
refreshInterval = setInterval(loadMessages, 5000);
// 立即加载一次,然后每秒请求增量更新
loadInitialMessages();
updateInterval = setInterval(loadIncrementalMessages, 1000); // 1秒更新一次
});
// 首次加载
function loadInitialMessages() {
// 清空现有内容
$('#sentMessages').empty();
$('#receivedMessages').empty();
sentIndex = 0;
receivedIndex = 0;
// 加载所有消息
loadMessages(true);
}
// 增量加载
function loadIncrementalMessages() {
loadMessages(false);
}
function loadMessages() {
function loadMessages(isInitial) {
$.ajax({
url: '/api/websocket/clients/' + encodeURIComponent(clientName) + '/messages',
url: `/api/websocket/clients/${encodeURIComponent(clientName)}/messages?sentStartIndex=${sentIndex}&receivedStartIndex=${receivedIndex}`,
type: 'GET',
success: function(data) {
displayMessages(data);
updateMessageList('sent', data.newSentMessages, data.totalSentCount, isInitial);
updateMessageList('received', data.newReceivedMessages, data.totalReceivedCount, isInitial);
// 更新下一次请求的起始索引
sentIndex = data.totalSentCount;
receivedIndex = data.totalReceivedCount;
},
error: function(xhr) {
if (xhr.status === 404) {
$('#sentMessages').html('<div class="text-warning text-center"><i class="fas fa-exclamation-triangle"></i> 客户端未连接</div>');
$('#receivedMessages').html('<div class="text-warning text-center"><i class="fas fa-exclamation-triangle"></i> 客户端未连接</div>');
} else {
$('#sentMessages').html('<div class="text-danger text-center"><i class="fas fa-times-circle"></i> 加载失败</div>');
$('#receivedMessages').html('<div class="text-danger text-center"><i class="fas fa-times-circle"></i> 加载失败</div>');
}
}
error: handleAjaxError
});
}
function displayMessages(data) {
// 更新计数
$('#sentCount').text(data.sentCount || 0);
$('#receivedCount').text(data.receivedCount || 0);
// 显示发送消息
var sentHtml = '';
if (data.sentMessages && data.sentMessages.length > 0) {
data.sentMessages.forEach(function(message, index) {
sentHtml += createMessageCard(message, index, 'sent');
});
} else {
sentHtml = '<div class="text-muted text-center"><i class="fas fa-info-circle"></i> 暂无发送消息</div>';
function updateMessageList(type, newMessages, totalCount, isInitial) {
if (isInitial && newMessages.length === 0) {
$(`#${type}Messages`).html('<div class="text-muted text-center"><i class="fas fa-info-circle"></i> 暂无消息</div>');
}
$('#sentMessages').html(sentHtml);
// 显示接收消息
var receivedHtml = '';
if (data.receivedMessages && data.receivedMessages.length > 0) {
data.receivedMessages.forEach(function(message, index) {
receivedHtml += createMessageCard(message, index, 'received');
});
} else {
receivedHtml = '<div class="text-muted text-center"><i class="fas fa-info-circle"></i> 暂无接收消息</div>';
if (!newMessages || newMessages.length === 0) {
$(`#${type}Count`).text(totalCount);
return;
}
$('#receivedMessages').html(receivedHtml);
// 高亮JSON语法
$('pre code').each(function() {
const container = $(`#${type}Messages`);
const fragment = $(document.createDocumentFragment());
// 如果是首次加载,先移除"暂无消息"的提示
if (isInitial) {
container.empty();
}
let currentIndex = (type === 'sent' ? sentIndex : receivedIndex);
newMessages.forEach(function(message) {
const card = createMessageCard(message, currentIndex, type);
fragment.append(card);
currentIndex++;
});
container.append(fragment);
// 数量限制
const messageCards = container.children('.card');
if (messageCards.length > MAX_MESSAGES) {
messageCards.slice(0, messageCards.length - MAX_MESSAGES).remove();
}
$(`#${type}Count`).text(totalCount);
// 高亮新添加的代码
container.find('pre code').not('.hljs').each(function() {
if (typeof hljs !== 'undefined') {
hljs.highlightElement(this);
}
});
}
function handleAjaxError(xhr) {
if (xhr.status !== 404) { // 忽略404,因为它可能是客户端未连接的正常状态
console.error("加载消息失败:", xhr.responseText);
}
}
function createMessageCard(message, index, type) {
var timestamp = new Date().toLocaleTimeString();
var messageType = type === 'sent' ? '发送' : '接收';
var bgClass = type === 'sent' ? 'border-success' : 'border-info';
var iconClass = type === 'sent' ? 'fas fa-paper-plane text-success' : 'fas fa-download text-info';
const timestamp = new Date().toLocaleTimeString();
const messageType = type === 'sent' ? '发送' : '接收';
const bgClass = type === 'sent' ? 'border-success' : 'border-info';
const iconClass = type === 'sent' ? 'fas fa-paper-plane text-success' : 'fas fa-download text-info';
return `
const cardHtml = `
<div class="card mb-2 ${bgClass}">
<div class="card-header py-2">
<small class="text-muted">
<i class="${iconClass}"></i> ${messageType} #${index + 1} - ${timestamp}
</small>
<button class="btn btn-sm btn-outline-secondary float-right" onclick="toggleMessage('${type}-${index}')">
<button class="btn btn-sm btn-outline-secondary float-right py-0" onclick="toggleMessage(this)">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<div class="card-body py-2" id="${type}-${index}" style="display: none;">
<pre><code class="json">${formatJson(message)}</code></pre>
<div class="card-body py-2" style="display: none;">
<pre><code class="json">${escapeHtml(formatJson(message))}</code></pre>
</div>
</div>
`;
return $(cardHtml); // 返回jQuery对象
}
function toggleMessage(id) {
$('#' + id).slideToggle();
function toggleMessage(button) {
$(button).closest('.card').find('.card-body').slideToggle('fast');
$(button).find('i').toggleClass('fa-chevron-down fa-chevron-up');
}
function formatJson(jsonString) {
@ -161,15 +194,27 @@
return jsonString;
}
}
function escapeHtml(text) {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function refreshMessages() {
loadMessages();
// 停止自动更新,手动刷新,然后重新开始
clearInterval(updateInterval);
loadInitialMessages();
updateInterval = setInterval(loadIncrementalMessages, 1000);
}
// 页面卸载时清除定时器
$(window).on('beforeunload', function() {
if (refreshInterval) {
clearInterval(refreshInterval);
if (updateInterval) {
clearInterval(updateInterval);
}
});
</script>
@ -177,5 +222,4 @@
<!-- 添加代码高亮支持 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
}
Loading…
Cancel
Save