HtmlToPdf/mvp/HtmlToPdfService.Core/Storage/LocalFileStorage.cs
2025-12-11 23:35:52 +08:00

202 lines
6.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using HtmlToPdfService.Core.Options;
namespace HtmlToPdfService.Core.Storage;
/// <summary>
/// 本地文件存储实现
/// </summary>
public class LocalFileStorage : IFileStorage
{
private readonly StorageOptions _options;
private readonly ILogger<LocalFileStorage> _logger;
public LocalFileStorage(IOptions<PdfServiceOptions> options, ILogger<LocalFileStorage> logger)
{
_options = options.Value.Storage;
_logger = logger;
// 确保存储目录存在
if (!Directory.Exists(_options.LocalPath))
{
Directory.CreateDirectory(_options.LocalPath);
_logger.LogInformation("创建存储目录: {Path}", _options.LocalPath);
}
}
/// <summary>
/// 保存 PDF 文件
/// </summary>
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;
}
}
/// <summary>
/// 获取文件
/// </summary>
public async Task<byte[]?> 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;
}
}
/// <summary>
/// 删除文件
/// </summary>
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;
}
/// <summary>
/// 清理过期文件
/// </summary>
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;
}
}