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.

386 lines
17 KiB

1 month ago
using Microsoft.AspNetCore.Mvc;
using LTEMvcApp.Models;
using LTEMvcApp.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Text;
1 month ago
using System.Linq;
1 month ago
namespace LTEMvcApp.Controllers
{
/// <summary>
/// 客户端消息管理控制器
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class MessageController : ControllerBase
{
private readonly WebSocketManagerService _webSocketManager;
private readonly ILogger<MessageController> _logger;
private readonly string _logsDirectory = "ClientMessageLogs";
public MessageController(WebSocketManagerService webSocketManager, ILogger<MessageController> logger)
{
_webSocketManager = webSocketManager;
_logger = logger;
if (!Directory.Exists(_logsDirectory))
{
Directory.CreateDirectory(_logsDirectory);
}
}
/// <summary>
/// SSE推送客户端消息流
/// </summary>
1 month ago
[HttpGet("{address}/stream")]
public async Task StreamClientMessages(string address)
1 month ago
{
try
{
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
Response.Headers.Append("Access-Control-Allow-Origin", "*");
1 month ago
var client = _webSocketManager.GetClientInstance(address);
1 month ago
if (client == null)
{
1 month ago
await SendSseEvent("error", new { message = "客户端未连接或不存在", address });
1 month ago
return;
}
await SendSseEvent("open", new {
message = "成功连接到服务器事件流",
1 month ago
address,
1 month ago
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(HttpContext.RequestAborted);
int lastSentCount = 0;
int lastReceivedCount = 0;
var cancellationToken = HttpContext.RequestAborted;
1 month ago
var sentLogFilePath = Path.Combine(_logsDirectory, $"{address}_sent_messages.log");
var receivedLogFilePath = Path.Combine(_logsDirectory, $"{address}_received_messages.log");
1 month ago
while (!cancellationToken.IsCancellationRequested)
{
try
{
bool hasNewMessages = false;
var currentSentCount = client.SentMessagesCount;
if (currentSentCount > lastSentCount)
{
var sentMessages = client.SentMessages?.ToList() ?? new List<string>();
if (sentMessages.Count > lastSentCount)
{
var newMessages = sentMessages.Skip(lastSentCount).ToList();
if (newMessages.Any())
{
1 month ago
await LogMessagesToFile(sentLogFilePath, newMessages, "SENT", address);
1 month ago
await SendSseEvent("update", new {
type = "sent",
messages = newMessages,
totalCount = currentSentCount,
newCount = newMessages.Count
});
lastSentCount = currentSentCount;
hasNewMessages = true;
}
}
}
var currentReceivedCount = client.ReceivedMessagesCount;
if (currentReceivedCount > lastReceivedCount)
{
var receivedMessages = client.ReceivedMessages?.ToList() ?? new List<string>();
if (receivedMessages.Count > lastReceivedCount)
{
var newMessages = receivedMessages.Skip(lastReceivedCount).ToList();
if (newMessages.Any())
{
1 month ago
await LogMessagesToFile(receivedLogFilePath, newMessages, "RECEIVED", address);
1 month ago
await SendSseEvent("update", new {
type = "received",
messages = newMessages,
totalCount = currentReceivedCount,
newCount = newMessages.Count
});
lastReceivedCount = currentReceivedCount;
hasNewMessages = true;
}
}
}
if (hasNewMessages)
{
await Response.Body.FlushAsync(cancellationToken);
}
await Task.Delay(250, cancellationToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
1 month ago
_logger.LogError(ex, "StreamClientMessages 循环中发生错误,客户端: {ClientName}", address);
1 month ago
await SendSseEvent("error", new {
message = "处理消息流时发生错误",
error = ex.Message,
1 month ago
address,
1 month ago
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
await Task.Delay(1000, cancellationToken);
}
}
await SendSseEvent("disconnected", new {
message = "客户端消息流连接已断开",
1 month ago
address,
1 month ago
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
}
catch (OperationCanceledException)
{
1 month ago
_logger.LogInformation("StreamClientMessages 连接被客户端取消,客户端: {ClientName}", address);
1 month ago
}
catch (Exception ex)
{
1 month ago
_logger.LogError(ex, "StreamClientMessages 方法执行时发生未处理的异常,客户端: {ClientName}", address);
1 month ago
try
{
await SendSseEvent("fatal_error", new {
message = "服务器内部错误",
error = ex.Message,
1 month ago
address,
1 month ago
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync();
}
catch { }
}
}
/// <summary>
/// 发送消息到客户端
/// </summary>
1 month ago
[HttpPost("{address}/send")]
public ActionResult SendMessage(string address, [FromBody] JObject message)
1 month ago
{
1 month ago
var messageId = _webSocketManager.SendMessageToClient(address, message);
1 month ago
if (messageId >= 0)
1 month ago
return Ok(new { messageId, message = $"消息已发送到客户端 '{address}'" });
1 month ago
else
1 month ago
return BadRequest($"发送消息到客户端 '{address}' 失败");
1 month ago
}
/// <summary>
/// 获取客户端消息日志文件列表
/// </summary>
[HttpGet("logs")]
public ActionResult<object> GetClientMessageLogFiles()
{
try
{
if (!Directory.Exists(_logsDirectory))
{
return Ok(new { files = new List<object>(), message = "日志目录不存在" });
}
var logFiles = Directory.GetFiles(_logsDirectory, "*.log")
.Select(filePath => new
{
fileName = Path.GetFileName(filePath),
filePath = filePath,
size = new FileInfo(filePath).Length,
lastModified = System.IO.File.GetLastWriteTime(filePath),
1 month ago
address = Path.GetFileNameWithoutExtension(filePath).Replace("_sent_messages", "").Replace("_received_messages", ""),
1 month ago
type = filePath.Contains("_sent_messages") ? "发送消息" : "接收消息"
})
.OrderByDescending(f => f.lastModified)
.ToList();
return Ok(new { files = logFiles, totalCount = logFiles.Count });
}
catch (Exception ex)
{
_logger.LogError(ex, "获取客户端消息日志文件列表时发生错误");
return StatusCode(500, new { message = "获取日志文件列表失败", error = ex.Message });
}
}
/// <summary>
/// 获取客户端消息日志文件内容
/// </summary>
[HttpGet("logs/{fileName}")]
public ActionResult<object> GetClientMessageLogContent(string fileName, [FromQuery] int lines = 100)
{
try
{
var filePath = Path.Combine(_logsDirectory, fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { message = $"日志文件 '{fileName}' 不存在" });
}
var fileInfo = new System.IO.FileInfo(filePath);
var allLines = System.IO.File.ReadAllLines(filePath, Encoding.UTF8);
var lastLines = allLines.TakeLast(lines).ToList();
return Ok(new
{
fileName = fileName,
filePath = filePath,
totalLines = allLines.Length,
returnedLines = lastLines.Count,
fileSize = fileInfo.Length,
lastModified = fileInfo.LastWriteTime,
content = lastLines
});
}
catch (Exception ex)
{
_logger.LogError(ex, "获取客户端消息日志文件内容时发生错误: {FileName}", fileName);
return StatusCode(500, new { message = "获取日志文件内容失败", error = ex.Message });
}
}
/// <summary>
/// 清空客户端消息日志文件
/// </summary>
[HttpDelete("logs")]
public ActionResult ClearClientMessageLogs([FromQuery] string? fileName = null)
{
try
{
if (!Directory.Exists(_logsDirectory))
{
return Ok(new { message = "日志目录不存在,无需清空" });
}
int clearedCount = 0;
if (string.IsNullOrEmpty(fileName))
{
var logFiles = Directory.GetFiles(_logsDirectory, "*.log");
foreach (var filePath in logFiles)
{
System.IO.File.WriteAllText(filePath, string.Empty);
clearedCount++;
}
_logger.LogInformation("已清空 {Count} 个客户端消息日志文件", clearedCount);
return Ok(new { message = $"已清空 {clearedCount} 个客户端消息日志文件" });
}
else
{
var filePath = Path.Combine(_logsDirectory, fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { message = $"日志文件 '{fileName}' 不存在" });
}
System.IO.File.WriteAllText(filePath, string.Empty);
_logger.LogInformation("已清空客户端消息日志文件: {FileName}", fileName);
return Ok(new { message = $"已清空客户端消息日志文件 '{fileName}'" });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "清空客户端消息日志文件时发生错误");
return StatusCode(500, new { message = "清空日志文件失败", error = ex.Message });
}
}
/// <summary>
/// 删除客户端消息日志文件
/// </summary>
[HttpDelete("logs/delete")]
public ActionResult DeleteClientMessageLogs([FromQuery] string? fileName = null)
{
try
{
if (!Directory.Exists(_logsDirectory))
{
return Ok(new { message = "日志目录不存在,无需删除" });
}
int deletedCount = 0;
if (string.IsNullOrEmpty(fileName))
{
var logFiles = Directory.GetFiles(_logsDirectory, "*.log");
foreach (var filePath in logFiles)
{
System.IO.File.Delete(filePath);
deletedCount++;
}
_logger.LogInformation("已删除 {Count} 个客户端消息日志文件", deletedCount);
return Ok(new { message = $"已删除 {deletedCount} 个客户端消息日志文件" });
}
else
{
var filePath = Path.Combine(_logsDirectory, fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { message = $"日志文件 '{fileName}' 不存在" });
}
System.IO.File.Delete(filePath);
_logger.LogInformation("已删除客户端消息日志文件: {FileName}", fileName);
return Ok(new { message = $"已删除客户端消息日志文件 '{fileName}'" });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "删除客户端消息日志文件时发生错误");
return StatusCode(500, new { message = "删除日志文件失败", error = ex.Message });
}
}
1 month ago
private async Task LogMessagesToFile(string logFilePath, List<string> messages, string messageType, string address)
1 month ago
{
try
{
1 month ago
if (messages == null || messages.Count == 0)
{
_logger.LogDebug("跳过空消息列表的日志写入: {MessageType}", messageType);
return;
}
var validMessages = messages.Where(msg => !string.IsNullOrWhiteSpace(msg)).ToList();
if (validMessages.Count == 0)
{
_logger.LogDebug("跳过无效消息的日志写入: {MessageType}", messageType);
return;
}
1 month ago
var logBuilder = new StringBuilder();
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
1 month ago
foreach (var message in validMessages)
1 month ago
{
1 month ago
logBuilder.AppendLine($"[{timestamp}] [{address}] [{messageType}] {message}");
1 month ago
logBuilder.AppendLine(new string('-', 80));
}
1 month ago
if (logBuilder.Length > 0)
{
await System.IO.File.AppendAllTextAsync(logFilePath, logBuilder.ToString(), Encoding.UTF8);
_logger.LogDebug("已记录 {Count} 条 {MessageType} 消息到文件: {FilePath}", validMessages.Count, messageType, logFilePath);
}
1 month ago
}
catch (Exception ex)
{
_logger.LogError(ex, "记录消息到文件时发生错误: {FilePath}", logFilePath);
}
}
private async Task SendSseEvent(string eventName, object data)
{
try
{
if (string.IsNullOrEmpty(eventName) || data == null)
return;
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
var eventData = $"event: {eventName}\ndata: {json}\n\n";
await Response.WriteAsync(eventData);
}
catch (Exception ex)
{
_logger.LogError(ex, "发送SSE事件时发生错误: {EventName}", eventName);
}
}
}
}