huangxiaoqiang
9 小时以前 960b33fa24c47a330e51a2c24859d681ae62caeb
Code Management/WCS/WIDESEA_WCSServer/WIDESEAWCS_Server/Controllers/System/Sys_LogController.cs
@@ -1,25 +1,28 @@
using Microsoft.AspNetCore.Authorization;
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_ITaskInfoService;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Server.Controllers.System
namespace WIDESEAWCSServer.Controllers
{
    [Route("api/Sys_Log")]
    [ApiController]
    public class Sys_LogController : ApiBaseController<ISys_LogService, Sys_Log>
    {
        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()
@@ -32,6 +35,201 @@
        {
            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<ActionResult> 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<FileInfo> 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<FileInfo> 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<FileInfo>(null);
        }
        private FileStream CreateFileStream(string filePath)
        {
            return new FileStream(
                filePath,
                FileMode.Open,
                FileAccess.Read,
                FileShare.ReadWrite, // 允许其他进程同时读取/写入
                81920, // 缓冲区大小
                useAsync: true);
        }
        /// <summary>
        /// 判断是否为文件被锁定的异常
        /// </summary>
        private bool IsFileLockedException(IOException ex)
        {
            int errorCode = ex.HResult & 0xFFFF;
            return errorCode == 32 || errorCode == 33; // ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION
        }
        /// <summary>
        /// 检查文件类型是否允许
        /// </summary>
        private bool IsAllowedFileType(string extension)
        {
            return ALLOWED_FILE_TYPES.Contains(extension);
        }
        /// <summary>
        /// 获取Content-Type
        /// </summary>
        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"
            };
        }
    }
}
}