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;
}
}