huangxiaoqiang
12 小时以前 960b33fa24c47a330e51a2c24859d681ae62caeb
Code Management/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/System/Sys_LogController.cs
@@ -1,23 +1,26 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Text;
using WIDESEAWCS_Core;
using WIDESEAWCS_ISystemServices;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Server.Controllers.System
namespace WIDESEAWMSServer.Controllers
{
    [Route("api/Sys_Log")]
    [ApiController]
    public class Sys_LogController : ApiBaseController<ISys_LogService, Sys_Log>
    {
        private const int MAX_FILE_SIZE_MB = 50;
        private const int MAX_RETRY_COUNT = 3;
        private const int RETRY_DELAY_MS = 100;
        private static readonly string[] ALLOWED_FILE_TYPES = { ".txt", ".log", ".csv", ".json", ".xml" };
        private readonly IHttpContextAccessor _httpContextAccessor;
        public Sys_LogController(ISys_LogService service, IHttpContextAccessor httpContextAccessor) : base(service)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        [HttpPost, Route("GetLogList"), AllowAnonymous]
        public WebResponseContent GetLogList()
@@ -30,6 +33,280 @@
        {
            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
            {
                // 1. 参数验证
                if (string.IsNullOrWhiteSpace(fileName))
                {
                    return BadRequest("文件名不能为空");
                }
                // 安全性检查:防止路径遍历攻击
                if (fileName.Contains("..") || Path.IsPathRooted(fileName))
                {
                    return BadRequest("无效的文件名");
                }
                //string logDirectory = Path.Combine(AppContext.BaseDirectory, "logs");
                string logDirectory = Path.Combine(AppContext.BaseDirectory);
                if (!Directory.Exists(logDirectory))
                {
                    Directory.CreateDirectory(logDirectory);
                }
                string filePath = Path.Combine(logDirectory, fileName);
                if (Directory.Exists(filePath))
                {
                    return NotFound($"文件 {fileName} 不存在");
                }
                string extension = Path.GetExtension(fileName).ToLowerInvariant();
                if (!IsAllowedFileType(extension))
                {
                    return BadRequest($"不支持的文件类型: {extension}");
                }
                FileInfo fileInfo = new FileInfo(filePath);
                if (fileInfo.Length > MAX_FILE_SIZE_MB * 1024 * 1024)
                {
                    return BadRequest($"文件过大,超过{MAX_FILE_SIZE_MB}MB限制");
                }
                // 方案1:使用重试机制 + 共享读取(推荐)
                byte[] fileBytes = await ReadFileWithRetryAsync(filePath);
                if (fileBytes == null)
                {
                    return StatusCode(500, "文件被占用,无法下载,请稍后重试");
                }
                string contentType = GetContentType(extension);
                // 设置下载头
                Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8)}\"");
                Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
                Response.Headers.Add("Pragma", "no-cache");
                Response.Headers.Add("Expires", "0");
                return File(fileBytes, 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}");
            }
        }
        /// <summary>
        /// 带重试机制的文件读取方法
        /// </summary>
        private async Task<byte[]> ReadFileWithRetryAsync(string filePath)
        {
            for (int attempt = 0; attempt < MAX_RETRY_COUNT; attempt++)
            {
                try
                {
                    // 使用 FileShare.ReadWrite 允许其他进程同时读取和写入
                    // 使用异步读取提高性能
                    using (var fileStream = new FileStream(
                        filePath,
                        FileMode.Open,
                        FileAccess.Read,
                        FileShare.ReadWrite | FileShare.Delete, // 允许删除操作
                        bufferSize: 4096,
                        useAsync: true))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            await fileStream.CopyToAsync(memoryStream);
                            return memoryStream.ToArray();
                        }
                    }
                }
                catch (IOException) when (attempt < MAX_RETRY_COUNT - 1)
                {
                    // 如果不是最后一次重试,等待一段时间
                    await Task.Delay(RETRY_DELAY_MS * (attempt + 1));
                }
                catch (IOException ex)
                {
                    // 最后一次尝试也失败了
                    throw;
                }
            }
            return null;
        }
        /// <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"
            };
        }
        /// <summary>
        /// 备选方案:创建临时副本下载(最安全,但性能稍差)
        /// </summary>
        [HttpPost, HttpGet, Route("DownLoadLogCopy"), AllowAnonymous]
        public virtual async Task<ActionResult> DownLoadLogCopy(string fileName)
        {
            try
            {
                // 参数验证(同上)
                if (string.IsNullOrWhiteSpace(fileName))
                {
                    return BadRequest("文件名不能为空");
                }
                string logDirectory = Path.Combine(AppContext.BaseDirectory, "logs");
                string filePath = Path.Combine(logDirectory, fileName);
                if (Directory.Exists(filePath))
                {
                    return NotFound($"文件 {fileName} 不存在");
                }
                // 生成临时文件名
                string tempFileName = $"{Path.GetFileNameWithoutExtension(fileName)}_{Guid.NewGuid():N}{Path.GetExtension(fileName)}";
                string tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName);
                try
                {
                    // 尝试复制文件到临时位置(使用重试机制)
                    bool copySuccess = false;
                    for (int attempt = 0; attempt < MAX_RETRY_COUNT; attempt++)
                    {
                        try
                        {
                            //Directory.GetFiles.Copy(filePath, tempFilePath, false);
                            copySuccess = true;
                            break;
                        }
                        catch (IOException) when (attempt < MAX_RETRY_COUNT - 1)
                        {
                            await Task.Delay(RETRY_DELAY_MS * (attempt + 1));
                        }
                    }
                    if (!copySuccess)
                    {
                        return StatusCode(500, "无法复制文件,可能被其他进程占用");
                    }
                    // 从临时文件读取
                    byte[] fileBytes;
                    using (FileStream tempStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        using (MemoryStream memoryStream = new MemoryStream())
                        {
                            await tempStream.CopyToAsync(memoryStream);
                            fileBytes = memoryStream.ToArray();
                        }
                    }
                    string extension = Path.GetExtension(fileName).ToLowerInvariant();
                    string contentType = GetContentType(extension);
                    // 返回文件后清理临时文件
                    var result = File(fileBytes, contentType, fileName);
                    // 异步清理临时文件
                    _ = Task.Run(() =>
                    {
                        try
                        {
                            Directory.Delete(tempFilePath);
                        }
                        catch
                        {
                            // 忽略删除失败
                        }
                    });
                    return result;
                }
                finally
                {
                    // 确保临时文件被清理
                    if (Directory.Exists(tempFilePath))
                    {
                        try
                        {
                            Directory.Delete(tempFilePath);
                        }
                        catch
                        {
                            // 忽略删除失败
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                return StatusCode(500, $"服务器内部错误: {ex.Message}");
            }
        }
    }
   
}