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<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()
|
{
|
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<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"
|
};
|
}
|
}
|
}
|