using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using HtmlToPdfService.Core.Options; namespace HtmlToPdfService.Core.Storage; /// /// 本地文件存储实现 /// public class LocalFileStorage : IFileStorage { private readonly StorageOptions _options; private readonly ILogger _logger; public LocalFileStorage(IOptions options, ILogger logger) { _options = options.Value.Storage; _logger = logger; // 确保存储目录存在 if (!Directory.Exists(_options.LocalPath)) { Directory.CreateDirectory(_options.LocalPath); _logger.LogInformation("创建存储目录: {Path}", _options.LocalPath); } } /// /// 保存 PDF 文件 /// public async Task<(string FilePath, string DownloadUrl)> SaveAsync( string requestId, byte[] pdfData, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(requestId)) throw new ArgumentException("请求 ID 不能为空", nameof(requestId)); if (pdfData == null || pdfData.Length == 0) throw new ArgumentException("PDF 数据不能为空", nameof(pdfData)); try { // 按日期组织目录结构:/pdfs/yyyy-MM-dd/ var dateFolder = DateTime.UtcNow.ToString("yyyy-MM-dd"); var dailyPath = Path.Combine(_options.LocalPath, dateFolder); if (!Directory.Exists(dailyPath)) { Directory.CreateDirectory(dailyPath); } var fileName = $"{requestId}.pdf"; var filePath = Path.Combine(dailyPath, fileName); // 保存文件 await File.WriteAllBytesAsync(filePath, pdfData, cancellationToken); _logger.LogInformation("PDF 文件已保存: {FilePath}, 大小: {Size} bytes", filePath, pdfData.Length); // 构造下载 URL(相对路径) var downloadUrl = $"/api/pdf/download/{requestId}"; return (filePath, downloadUrl); } catch (Exception ex) { _logger.LogError(ex, "保存 PDF 文件失败: {RequestId}", requestId); throw; } } /// /// 获取文件 /// public async Task GetAsync(string requestId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(requestId)) throw new ArgumentException("请求 ID 不能为空", nameof(requestId)); try { // 搜索最近几天的目录 var searchDays = Math.Max(1, _options.RetentionHours / 24 + 1); for (int i = 0; i < searchDays; i++) { var date = DateTime.UtcNow.AddDays(-i).ToString("yyyy-MM-dd"); var filePath = Path.Combine(_options.LocalPath, date, $"{requestId}.pdf"); if (File.Exists(filePath)) { _logger.LogDebug("找到 PDF 文件: {FilePath}", filePath); return await File.ReadAllBytesAsync(filePath, cancellationToken); } } _logger.LogWarning("未找到 PDF 文件: {RequestId}", requestId); return null; } catch (Exception ex) { _logger.LogError(ex, "读取 PDF 文件失败: {RequestId}", requestId); throw; } } /// /// 删除文件 /// public Task DeleteAsync(string requestId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(requestId)) throw new ArgumentException("请求 ID 不能为空", nameof(requestId)); try { // 搜索并删除文件 var searchDays = Math.Max(1, _options.RetentionHours / 24 + 1); for (int i = 0; i < searchDays; i++) { var date = DateTime.UtcNow.AddDays(-i).ToString("yyyy-MM-dd"); var filePath = Path.Combine(_options.LocalPath, date, $"{requestId}.pdf"); if (File.Exists(filePath)) { File.Delete(filePath); _logger.LogInformation("已删除 PDF 文件: {FilePath}", filePath); break; } } } catch (Exception ex) { _logger.LogError(ex, "删除 PDF 文件失败: {RequestId}", requestId); throw; } return Task.CompletedTask; } /// /// 清理过期文件 /// public Task CleanupExpiredFilesAsync(CancellationToken cancellationToken = default) { try { if (!Directory.Exists(_options.LocalPath)) return Task.CompletedTask; var expirationTime = DateTime.UtcNow.AddHours(-_options.RetentionHours); var deletedCount = 0; // 遍历所有日期目录 var directories = Directory.GetDirectories(_options.LocalPath); foreach (var directory in directories) { if (cancellationToken.IsCancellationRequested) break; try { // 检查目录中的文件 var files = Directory.GetFiles(directory, "*.pdf"); foreach (var file in files) { var fileInfo = new FileInfo(file); if (fileInfo.LastWriteTimeUtc < expirationTime) { File.Delete(file); deletedCount++; } } // 如果目录为空,删除目录 if (!Directory.EnumerateFileSystemEntries(directory).Any()) { Directory.Delete(directory); } } catch (Exception ex) { _logger.LogError(ex, "清理目录失败: {Directory}", directory); } } if (deletedCount > 0) { _logger.LogInformation("清理过期文件完成,删除了 {Count} 个文件", deletedCount); } } catch (Exception ex) { _logger.LogError(ex, "清理过期文件失败"); } return Task.CompletedTask; } }