using Model;
using Model.Dto.System;
using Model.Entity.System;
using Service.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Service.Implement
{
///
/// 本地文件系统存储 Provider
/// 落地策略:存到 BasePath/{Bucket?}/{yyyy}/{MM}/{dd}/{guid}{ext},URL = {Endpoint}/{Bucket?}/{yyyy}/{MM}/{dd}/{guid}{ext}
/// wwwroot/uploads 由 Program.UseStaticFiles 提供访问;MinIO/OSS 实现见 IFileStorageProvider 扩展点
///
public class LocalFileStorageProvider : IFileStorageProvider
{
public string ProviderName => "local";
public async Task> UploadAsync(Stream stream, string originalName, long size, FileStorageConfigEntity config)
{
if (config == null) return Result.Error("存储配置不能为空");
if (stream == null || !stream.CanRead) return Result.Error("文件流不可读");
try
{
var basePath = ResolveBasePath(config.BasePath);
if (string.IsNullOrWhiteSpace(basePath))
return Result.Error("Local 存储必须配置 BasePath");
var ext = Path.GetExtension(originalName)?.ToLowerInvariant() ?? "";
var now = DateTime.Now;
var datePath = $"{now:yyyy}/{now:MM}/{now:dd}";
var fileName = $"{Guid.NewGuid():N}{ext}";
var storedFileName = string.IsNullOrWhiteSpace(config.Bucket)
? $"{datePath}/{fileName}"
: $"{config.Bucket}/{datePath}/{fileName}";
var dir = Path.Combine(basePath,
string.IsNullOrWhiteSpace(config.Bucket) ? datePath : Path.Combine(config.Bucket, datePath));
Directory.CreateDirectory(dir);
var fullPath = Path.Combine(dir, fileName);
using (var fs = new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
await stream.CopyToAsync(fs);
}
var url = BuildUrl(config.Endpoint, config.Bucket, storedFileName);
return Result.Success(new FileUploadResultDto
{
FileName = storedFileName,
Url = url,
Ext = ext,
Size = size,
Provider = ProviderName
});
}
catch (Exception ex)
{
return Result.Error("本地文件上传失败", ex);
}
}
public Task DeleteAsync(string storedFileName, FileStorageConfigEntity config)
{
if (config == null || string.IsNullOrWhiteSpace(storedFileName))
return Task.FromResult(Result.Error("存储配置或文件名不能为空"));
try
{
var basePath = ResolveBasePath(config.BasePath);
var fullPath = Path.Combine(basePath, storedFileName.Replace('/', Path.DirectorySeparatorChar));
if (File.Exists(fullPath)) File.Delete(fullPath);
return Task.FromResult(Result.Success());
}
catch (Exception ex)
{
return Task.FromResult(Result.Error("删除本地文件失败", ex));
}
}
public Task TestConnectionAsync(FileStorageConfigEntity config)
{
if (config == null) return Task.FromResult(Result.Error("存储配置不能为空"));
try
{
var basePath = ResolveBasePath(config.BasePath);
if (string.IsNullOrWhiteSpace(basePath))
return Task.FromResult(Result.Error("Local 存储必须配置 BasePath"));
Directory.CreateDirectory(basePath);
var testFile = Path.Combine(basePath, $".storage_test_{Guid.NewGuid():N}");
File.WriteAllText(testFile, "ok");
File.Delete(testFile);
return Task.FromResult(Result.Success());
}
catch (Exception ex)
{
return Task.FromResult(Result.Error("Local 连接测试失败:" + ex.Message));
}
}
// ==================== 私有工具 ====================
private static string ResolveBasePath(string? basePath)
{
if (string.IsNullOrWhiteSpace(basePath)) return string.Empty;
// 相对路径以运行目录为基准(如 wwwroot/uploads)
return Path.IsPathRooted(basePath) ? basePath : Path.Combine(AppContext.BaseDirectory, basePath);
}
private static string BuildUrl(string? endpoint, string? bucket, string storedFileName)
{
var segs = new List();
if (!string.IsNullOrWhiteSpace(endpoint)) segs.Add(endpoint.TrimEnd('/'));
if (!string.IsNullOrWhiteSpace(bucket)) segs.Add(bucket.Trim('/'));
segs.Add(storedFileName);
return string.Join('/', segs);
}
}
}