using Autofac.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Text; using WIDESEAWCS_Core; using WIDESEAWCS_Core.BaseController; using WIDESEAWCS_DTO.AGV; using WIDESEAWCS_ISystemServices; using WIDESEAWCS_Model.Models; namespace WIDESEAWCSServer.Controllers { [Route("api/Sys_Log")] [ApiController] public class Sys_LogController : ApiBaseController { private const int MAX_FILE_SIZE_MB = 50; private static readonly string[] ALLOWED_FILE_TYPES = { ".txt", ".log", ".csv", ".json", ".xml" }; private readonly string[] LogFolders = { "Log", "Log_PLCReadWrite" }; private readonly IHttpContextAccessor _httpContextAccessor; public Sys_LogController(ISys_LogService service, IHttpContextAccessor httpContextAccessor) : base(service) { _httpContextAccessor = httpContextAccessor; } [HttpPost, Route("GetLogList"), AllowAnonymous] public WebResponseContent GetLogList() { return Service.GetLogList(); } [HttpPost, Route("GetLogData"), AllowAnonymous] public WebResponseContent GetLogData([FromBody] GetLogParm parm) { return Service.GetLogData(parm); } [HttpPost, Route("GetLogName"), AllowAnonymous] public WebResponseContent GetLogName() { return Service.GetLogName(); } [HttpPost, Route("GetLog"), AllowAnonymous] public WebResponseContent GetLog(string fileName) { return Service.GetLog(fileName); } [HttpPost, HttpGet, Route("DownLoadLog"), AllowAnonymous] public virtual async Task DownLoadLog(string fileName) { try { // 参数验证 if (string.IsNullOrWhiteSpace(fileName)) { return BadRequest("文件名不能为空"); } // 安全性检查 if (fileName.Contains("..") || Path.IsPathRooted(fileName) || fileName.Contains("/") || fileName.Contains("\\")) { return BadRequest("无效的文件名"); } // 检查文件类型 string extension = Path.GetExtension(fileName).ToLowerInvariant(); if (!IsAllowedFileType(extension)) { return BadRequest($"不支持的文件类型: {extension}"); } // 查找文件 var fileInfo = await FindLogFileAsync(fileName); if (fileInfo == null) { return NotFound($"文件 {fileName} 不存在"); } // 检查文件大小(仅用于限制,不加载到内存) if (fileInfo.Length > MAX_FILE_SIZE_MB * 1024 * 1024) { return BadRequest($"文件过大,超过{MAX_FILE_SIZE_MB}MB限制"); } string contentType = GetContentType(extension); // 设置下载头 Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8)}\""); Response.Headers.Add("Content-Length", fileInfo.Length.ToString()); Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); Response.Headers.Add("Pragma", "no-cache"); Response.Headers.Add("Expires", "0"); // 流式返回,不加载整个文件到内存 return File(CreateFileStream(fileInfo.FullName), contentType, fileName); } catch (UnauthorizedAccessException) { return StatusCode(403, "没有访问该文件的权限"); } catch (PathTooLongException) { return BadRequest("文件路径过长"); } catch (IOException ex) { if (IsFileLockedException(ex)) { return StatusCode(500, "文件被锁定,可能正在被系统写入,请稍后再试"); } return StatusCode(500, $"文件读取失败: {ex.Message}"); } catch (Exception ex) { return StatusCode(500, $"服务器内部错误: {ex.Message}"); } } private async Task FindLogFileAsync(string fileName) { var tasks = LogFolders.Select(folderName => Task.Run(() => { var folderPath = Path.Combine(AppContext.BaseDirectory, folderName); var folderInfo = new DirectoryInfo(folderPath); if (!folderInfo.Exists) { return null; } return FindFileInFolderAsync(folderInfo, fileName); })); var results = await Task.WhenAll(tasks); return results.FirstOrDefault(result => result != null); } private async Task FindFileInFolderAsync(DirectoryInfo folder, string fileName) { try { // 先查找当前目录下的文件 var files = folder.GetFiles(); var file = files.FirstOrDefault(x => x.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase)); if (file != null) { return await Task.FromResult(file); } // 递归查找子目录(排除Info目录) foreach (var subDir in folder.GetDirectories()) { // 跳过Info目录 if (subDir.Name.Equals("Info", StringComparison.OrdinalIgnoreCase)) { continue; } // 跳过无法访问的目录 try { file = await FindFileInFolderAsync(subDir, fileName); if (file != null) { return file; } } catch (UnauthorizedAccessException) { // 跳过无权限访问的目录 continue; } } } catch (UnauthorizedAccessException) { // 忽略无权访问的目录 } catch (DirectoryNotFoundException) { // 忽略不存在的目录 } return await Task.FromResult(null); } private FileStream CreateFileStream(string filePath) { return new FileStream( filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, // 允许其他进程同时读取/写入 81920, // 缓冲区大小 useAsync: true); } /// /// 判断是否为文件被锁定的异常 /// private bool IsFileLockedException(IOException ex) { int errorCode = ex.HResult & 0xFFFF; return errorCode == 32 || errorCode == 33; // ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION } /// /// 检查文件类型是否允许 /// private bool IsAllowedFileType(string extension) { return ALLOWED_FILE_TYPES.Contains(extension); } /// /// 获取Content-Type /// private string GetContentType(string extension) { return extension.ToLowerInvariant() switch { ".txt" => "text/plain; charset=utf-8", ".log" => "text/plain; charset=utf-8", ".csv" => "text/csv; charset=utf-8", ".json" => "application/json; charset=utf-8", ".xml" => "application/xml; charset=utf-8", _ => "application/octet-stream" }; } } }