using Foxel.Services.Attributes; using Foxel.Services.Configuration; using Microsoft.Extensions.Logging; namespace Foxel.Services.Storage.Providers; public class LocalStorageConfig { public string BasePath { get; set; } = string.Empty; public string ServerUrl { get; set; } = string.Empty; public string PublicBasePath { get; set; } = "/Uploads"; } [StorageProvider(StorageType.Local)] public class LocalStorageProvider : IStorageProvider { private readonly LocalStorageConfig _config; private readonly ILogger _logger; public LocalStorageProvider(LocalStorageConfig config, ILogger logger) { _config = config; _logger = logger; if (string.IsNullOrWhiteSpace(_config.BasePath)) { var defaultPath = Path.Combine(Directory.GetCurrentDirectory(), "DefaultUploads"); _logger.LogWarning("LocalStorageConfig.BasePath 未配置,将使用默认路径: {DefaultPath}", defaultPath); throw new InvalidOperationException("LocalStorageConfig.BasePath 必须在配置中提供。"); } Directory.CreateDirectory(_config.BasePath); } public async Task SaveAsync(Stream fileStream, string fileName, string contentType) { try { string currentDate = DateTime.Now.ToString("yyyy/MM"); string folder = Path.Combine(_config.BasePath, currentDate); Directory.CreateDirectory(folder); string newFileName = fileName; string filePath = Path.Combine(folder, newFileName); await using var output = new FileStream(filePath, FileMode.Create); await fileStream.CopyToAsync(output); return $"{_config.PublicBasePath.TrimEnd('/')}/{currentDate}/{newFileName}"; } catch (Exception ex) { _logger.LogError(ex, "保存文件到本地存储时出错。BasePath: {BasePath}, FileName: {FileName}", _config.BasePath, fileName); throw; } } public Task DeleteAsync(string storagePath) { try { string relativePath = storagePath; if (!string.IsNullOrEmpty(_config.PublicBasePath) && storagePath.StartsWith(_config.PublicBasePath)) { relativePath = storagePath.Substring(_config.PublicBasePath.Length); } string fullPath = Path.Combine(_config.BasePath, relativePath.TrimStart('/')); if (File.Exists(fullPath)) { File.Delete(fullPath); _logger.LogInformation("已删除本地文件: {FullPath}", fullPath); } else { _logger.LogWarning("尝试删除本地文件但文件未找到: {FullPath}", fullPath); } } catch (Exception ex) { _logger.LogError(ex, "删除本地文件时出错。StoragePath: {StoragePath}, BasePath: {BasePath}", storagePath, _config.BasePath); } return Task.CompletedTask; } public string GetUrl(int pictureId,string? storagePath) { if (string.IsNullOrEmpty(storagePath)) return $"/images/unavailable.gif"; string serverUrl = _config.ServerUrl.TrimEnd('/'); return string.IsNullOrEmpty(serverUrl) ? storagePath : $"{serverUrl}{storagePath}"; } public Task DownloadFileAsync(string storagePath) { try { string relativePath = storagePath; if (!string.IsNullOrEmpty(_config.PublicBasePath) && storagePath.StartsWith(_config.PublicBasePath)) { relativePath = storagePath.Substring(_config.PublicBasePath.Length); } string fullPath = Path.Combine(_config.BasePath, relativePath.TrimStart('/')); if (!File.Exists(fullPath)) { _logger.LogError("尝试下载但文件未找到: {FullPath}", fullPath); throw new FileNotFoundException($"本地存储中找不到文件: {fullPath}", fullPath); } string tempFileName = Path.GetRandomFileName(); if (Path.HasExtension(fullPath)) { tempFileName = Path.ChangeExtension(tempFileName, Path.GetExtension(fullPath)); } string tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName); File.Copy(fullPath, tempFilePath, true); _logger.LogInformation("已将文件 {FullPath} 复制到临时位置 {TempFilePath} 以供下载/处理", fullPath, tempFilePath); return Task.FromResult(tempFilePath); // 返回临时文件的路径 } catch (Exception ex) { _logger.LogError(ex, "下载本地文件时出错。StoragePath: {StoragePath}, BasePath: {BasePath}", storagePath, _config.BasePath); throw; } } }