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; namespace LTEMvcApp.Controllers { /// /// 客户端消息管理控制器 /// [ApiController] [Route("api/[controller]")] public class MessageController : ControllerBase { private readonly WebSocketManagerService _webSocketManager; private readonly ILogger _logger; private readonly string _logsDirectory = "ClientMessageLogs"; public MessageController(WebSocketManagerService webSocketManager, ILogger logger) { _webSocketManager = webSocketManager; _logger = logger; if (!Directory.Exists(_logsDirectory)) { Directory.CreateDirectory(_logsDirectory); } } /// /// SSE推送客户端消息流 /// [HttpGet("{clientName}/stream")] public async Task StreamClientMessages(string clientName) { 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", "*"); var client = _webSocketManager.GetClientInstance(clientName); if (client == null) { await SendSseEvent("error", new { message = "客户端未连接或不存在", clientName }); return; } await SendSseEvent("open", new { message = "成功连接到服务器事件流", clientName, timestamp = DateTime.UtcNow }); await Response.Body.FlushAsync(HttpContext.RequestAborted); int lastSentCount = 0; int lastReceivedCount = 0; var cancellationToken = HttpContext.RequestAborted; var sentLogFilePath = Path.Combine(_logsDirectory, $"{clientName}_sent_messages.log"); var receivedLogFilePath = Path.Combine(_logsDirectory, $"{clientName}_received_messages.log"); while (!cancellationToken.IsCancellationRequested) { try { bool hasNewMessages = false; var currentSentCount = client.SentMessagesCount; if (currentSentCount > lastSentCount) { var sentMessages = client.SentMessages?.ToList() ?? new List(); if (sentMessages.Count > lastSentCount) { var newMessages = sentMessages.Skip(lastSentCount).ToList(); if (newMessages.Any()) { await LogMessagesToFile(sentLogFilePath, newMessages, "SENT", clientName); 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(); if (receivedMessages.Count > lastReceivedCount) { var newMessages = receivedMessages.Skip(lastReceivedCount).ToList(); if (newMessages.Any()) { await LogMessagesToFile(receivedLogFilePath, newMessages, "RECEIVED", clientName); 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) { _logger.LogError(ex, "StreamClientMessages 循环中发生错误,客户端: {ClientName}", clientName); await SendSseEvent("error", new { message = "处理消息流时发生错误", error = ex.Message, clientName, timestamp = DateTime.UtcNow }); await Response.Body.FlushAsync(cancellationToken); await Task.Delay(1000, cancellationToken); } } await SendSseEvent("disconnected", new { message = "客户端消息流连接已断开", clientName, timestamp = DateTime.UtcNow }); await Response.Body.FlushAsync(cancellationToken); } catch (OperationCanceledException) { _logger.LogInformation("StreamClientMessages 连接被客户端取消,客户端: {ClientName}", clientName); } catch (Exception ex) { _logger.LogError(ex, "StreamClientMessages 方法执行时发生未处理的异常,客户端: {ClientName}", clientName); try { await SendSseEvent("fatal_error", new { message = "服务器内部错误", error = ex.Message, clientName, timestamp = DateTime.UtcNow }); await Response.Body.FlushAsync(); } catch { } } } /// /// 发送消息到客户端 /// [HttpPost("{clientName}/send")] public ActionResult SendMessage(string clientName, [FromBody] JObject message) { var messageId = _webSocketManager.SendMessageToClient(clientName, message); if (messageId >= 0) return Ok(new { messageId, message = $"消息已发送到客户端 '{clientName}'" }); else return BadRequest($"发送消息到客户端 '{clientName}' 失败"); } /// /// 获取客户端消息日志文件列表 /// [HttpGet("logs")] public ActionResult GetClientMessageLogFiles() { try { if (!Directory.Exists(_logsDirectory)) { return Ok(new { files = new List(), 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), clientName = Path.GetFileNameWithoutExtension(filePath).Replace("_sent_messages", "").Replace("_received_messages", ""), 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 }); } } /// /// 获取客户端消息日志文件内容 /// [HttpGet("logs/{fileName}")] public ActionResult 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 }); } } /// /// 清空客户端消息日志文件 /// [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 }); } } /// /// 删除客户端消息日志文件 /// [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 }); } } private async Task LogMessagesToFile(string logFilePath, List messages, string messageType, string clientName) { try { var logBuilder = new StringBuilder(); var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); foreach (var message in messages) { logBuilder.AppendLine($"[{timestamp}] [{clientName}] [{messageType}] {message}"); logBuilder.AppendLine(new string('-', 80)); } await System.IO.File.AppendAllTextAsync(logFilePath, logBuilder.ToString(), Encoding.UTF8); _logger.LogDebug("已记录 {Count} 条 {MessageType} 消息到文件: {FilePath}", messages.Count, messageType, logFilePath); } 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); } } } }