huangxiaoqiang
7 小时以前 960b33fa24c47a330e51a2c24859d681ae62caeb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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"
            };
        }
    }
}