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