| | |
| | | using Microsoft.AspNetCore.Http; |
| | | using Microsoft.AspNetCore.Authorization; |
| | | using Microsoft.AspNetCore.Http; |
| | | using Microsoft.AspNetCore.Mvc; |
| | | using System.Text; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Core.BaseController; |
| | | using WIDESEA_ISystemService; |
| | | using WIDESEA_Model.Models; |
| | |
| | | [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" }; |
| | | |
| | | public Sys_LogController(ISys_LogService service) : base(service) |
| | | { |
| | | } |
| | | |
| | | [HttpPost, Route("GetLogName"), AllowAnonymous] |
| | | public WebResponseContent GetLogName() |
| | | { |
| | | WebResponseContent content = new WebResponseContent(); |
| | | try |
| | | { |
| | | List<object> data = new List<object>(); |
| | | DirectoryInfo folder = new DirectoryInfo(AppContext.BaseDirectory + "\\logs\\"); |
| | | DirectoryInfo[] firstDirectoryInfos = folder.GetDirectories().OrderByDescending(x => x.CreationTime).ToArray(); |
| | | int k = 2020; |
| | | for (int i = 0; i < firstDirectoryInfos.Length; i++) |
| | | { |
| | | if (firstDirectoryInfos[i].Name != "Info") |
| | | { |
| | | FileInfo[] nextFileInfos = firstDirectoryInfos[i].GetFiles(); |
| | | List<object> values = new List<object>(); |
| | | for (int j = 0; j < nextFileInfos.Length; j++) |
| | | { |
| | | values.Add(new { label = nextFileInfos[j].Name, id = k, hidden = true, fatherNode = firstDirectoryInfos[i].Name }); |
| | | k++; |
| | | } |
| | | data.Add(new { label = firstDirectoryInfos[i].Name, children = values, id = i, hidden = false }); |
| | | } |
| | | } |
| | | |
| | | FileInfo[] nextFileInfo = folder.GetFiles(); |
| | | List<object> value = new List<object>(); |
| | | for (int j = 0; j < nextFileInfo.Length; j++) |
| | | { |
| | | value.Add(new { label = nextFileInfo[j].Name, id = k, hidden = true, fatherNode = folder.Name }); |
| | | k++; |
| | | } |
| | | data.Add(new { label = folder.Name, children = value, id = 1, hidden = false }); |
| | | |
| | | return WebResponseContent.Instance.OK(data: data); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error(ex.Message); |
| | | } |
| | | } |
| | | |
| | | [HttpPost, Route("GetLog"), AllowAnonymous] |
| | | public WebResponseContent GetLog(string fileName) |
| | | { |
| | | WebResponseContent content = new WebResponseContent(); |
| | | try |
| | | { |
| | | List<FileInfo> files = new List<FileInfo>(); |
| | | DirectoryInfo folder = new DirectoryInfo(AppContext.BaseDirectory + "\\logs\\"); |
| | | DirectoryInfo[] firstDirectoryInfos = folder.GetDirectories(); |
| | | for (int i = 0; i < firstDirectoryInfos.Length; i++) |
| | | { |
| | | FileInfo[] nextFileInfos = firstDirectoryInfos[i].GetFiles(); |
| | | files.AddRange(nextFileInfos); |
| | | } |
| | | |
| | | FileInfo[] nextFileInfo = folder.GetFiles(); |
| | | files.AddRange(nextFileInfo); |
| | | if (files.Count > 0) |
| | | { |
| | | FileInfo file = files.Where(x => x.Name == fileName).FirstOrDefault(); |
| | | if (file == null) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æªæ¾å°æ¥å¿æä»¶: {fileName}"); |
| | | } |
| | | |
| | | // 使ç¨å
±äº«è¯»åæ¨¡å¼ |
| | | using (FileStream stream = new FileStream( |
| | | file.FullName, |
| | | FileMode.Open, |
| | | FileAccess.Read, |
| | | FileShare.ReadWrite)) |
| | | using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) |
| | | { |
| | | StringBuilder text = new StringBuilder(); |
| | | List<string> lines = new List<string>(); |
| | | while (!reader.EndOfStream) |
| | | { |
| | | var line = reader.ReadLine(); |
| | | lines.Add(line); |
| | | } |
| | | |
| | | content = WebResponseContent.Instance.OK(data: lines); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | content = WebResponseContent.Instance.Error($"æªæ¾å°æ¥å¿æä»¶,ã{fileName}ã"); |
| | | } |
| | | } |
| | | catch (IOException ex) |
| | | { |
| | | if (IsFileLockedException(ex)) |
| | | { |
| | | content = WebResponseContent.Instance.Error($"æ¥å¿æä»¶æ£å¨è¢«ç³»ç»åå
¥ï¼è¯·ç¨ååè¯"); |
| | | } |
| | | else |
| | | { |
| | | content = WebResponseContent.Instance.Error($"æå¼æ¥å¿æä»¶é误,{ex.Message}"); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | content = WebResponseContent.Instance.Error($"æå¼æ¥å¿æä»¶é误,{ex.Message}"); |
| | | } |
| | | return content; |
| | | } |
| | | |
| | | [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}"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |