using MiAssessment.Admin.Business.Models.Upload;
using MiAssessment.Admin.Business.Services.Interfaces;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace MiAssessment.Admin.Business.Services.Storage;
///
/// 本地存储提供者
/// 将文件保存到 wwwroot/uploads/{yyyy}/{MM}/{dd}/ 目录
///
public class LocalStorageProvider : IStorageProvider
{
private readonly IWebHostEnvironment _environment;
private readonly ILogger _logger;
private const string UploadBasePath = "uploads";
public LocalStorageProvider(
IWebHostEnvironment environment,
ILogger logger)
{
_environment = environment;
_logger = logger;
}
///
public string StorageType => "1";
///
public bool SupportsDirectUpload => false;
///
public async Task UploadAsync(Stream fileStream, string fileName, string contentType)
{
try
{
// 生成日期目录路径: uploads/2026/01/19/
var now = DateTime.Now;
var datePath = Path.Combine(
now.Year.ToString(),
now.Month.ToString("D2"),
now.Day.ToString("D2"));
// 生成唯一文件名
var uniqueFileName = GenerateUniqueFileName(fileName);
// 构建完整的物理路径
var relativePath = Path.Combine(UploadBasePath, datePath);
var physicalPath = Path.Combine(_environment.WebRootPath, relativePath);
// 确保目录存在
EnsureDirectoryExists(physicalPath);
// 完整文件路径
var fullFilePath = Path.Combine(physicalPath, uniqueFileName);
// 保存文件
await using var fileStreamOutput = new FileStream(fullFilePath, FileMode.Create, FileAccess.Write);
await fileStream.CopyToAsync(fileStreamOutput);
// 生成相对URL路径 (使用正斜杠)
var url = $"/{UploadBasePath}/{datePath.Replace(Path.DirectorySeparatorChar, '/')}/{uniqueFileName}";
_logger.LogInformation("本地存储上传成功: {FileName} -> {Url}", fileName, url);
return UploadResult.Ok(url);
}
catch (Exception ex)
{
_logger.LogError(ex, "本地存储上传失败: {FileName}", fileName);
return UploadResult.Fail($"保存文件失败: {ex.Message}");
}
}
///
public Task DeleteAsync(string fileUrl)
{
try
{
if (string.IsNullOrWhiteSpace(fileUrl))
{
return Task.FromResult(false);
}
// 将URL转换为物理路径
// URL格式: /uploads/2026/01/19/xxx.jpg
var relativePath = fileUrl.TrimStart('/').Replace('/', Path.DirectorySeparatorChar);
var physicalPath = Path.Combine(_environment.WebRootPath, relativePath);
if (File.Exists(physicalPath))
{
File.Delete(physicalPath);
_logger.LogInformation("本地存储删除成功: {Url}", fileUrl);
return Task.FromResult(true);
}
_logger.LogWarning("本地存储删除失败,文件不存在: {Url}", fileUrl);
return Task.FromResult(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "本地存储删除失败: {Url}", fileUrl);
return Task.FromResult(false);
}
}
///
public Task GetPresignedUploadUrlAsync(string fileName, string contentType, int expiresInSeconds = 600)
{
// 本地存储不支持客户端直传,返回null
_logger.LogDebug("本地存储不支持客户端直传");
return Task.FromResult(null);
}
///
/// 生成唯一文件名
/// 格式: {timestamp}_{guid}{extension}
///
private static string GenerateUniqueFileName(string originalFileName)
{
var extension = Path.GetExtension(originalFileName).ToLowerInvariant();
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var guid = Guid.NewGuid().ToString("N")[..8]; // 取GUID前8位
return $"{timestamp}_{guid}{extension}";
}
///
/// 确保目录存在,不存在则创建
///
private void EnsureDirectoryExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
_logger.LogDebug("创建目录: {Path}", path);
}
}
}