using Model;
using Model.Dto.System;
using Model.Entity.System;
using Model.Mapper;
using ORM;
using Service.Interface;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Service.Implement
{
///
/// 文件存储管理 服务实现
/// 通过 IEnumerable<IFileStorageProvider> 按配置 Provider 字段挑选实现;当前仅 Local 落地
///
public class FileStorageService : IFileStorageService
{
private readonly IEnumerable _providers;
private readonly ICurrentUser _currentUser;
// 掩码标记:前端回显 AccessKey/SecretKey 时用 **** 脱敏,回写时若仍为掩码则保留原值
private const string MaskMarker = "****";
public FileStorageService(IEnumerable providers, ICurrentUser currentUser)
{
_providers = providers;
_currentUser = currentUser;
}
// ==================== 存储配置 ====================
public async Task>> GetListPagedAsync(int pageIndex, int pageSize, RefAsync total, string? keyword = null)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.IsDel == 0)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.Name.Contains(keyword!) || x.Provider.Contains(keyword!))
.OrderBy(x => x.IsDefault, OrderByType.Desc).OrderBy(x => x.CreateTime, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize, total);
var dtos = list.ToDtoList();
// AccessKey/SecretKey 脱敏回显
foreach (var d in dtos) { d.AccessKey = Mask(d.AccessKey); d.SecretKey = Mask(d.SecretKey); }
return Result>.Success(dtos);
}
catch (Exception ex)
{
return Result>.Error("查询存储配置失败", ex);
}
}
public async Task> GetByIdAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("存储配置不存在或已被删除");
var dto = entity.ToDto();
dto.AccessKey = Mask(dto.AccessKey);
dto.SecretKey = Mask(dto.SecretKey);
return Result.Success(dto);
}
catch (Exception ex)
{
return Result.Error("查询存储配置失败", ex);
}
}
public async Task> AddAsync(FileStorageConfigDto dto)
{
if (dto == null || string.IsNullOrWhiteSpace(dto.Provider) || string.IsNullOrWhiteSpace(dto.Name))
return Result.Error("Provider 和名称不能为空");
if (!IsProviderSupported(dto.Provider))
return Result.Error($"不支持的 Provider:{dto.Provider}(当前仅支持 local)");
try
{
var entity = dto.ToEntity();
entity.Id = 0;
entity.CreateTime = DateTime.Now;
if (entity.IsDefault == 1) await ClearOtherDefaultAsync(0);
var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync();
entity.Id = id;
var result = entity.ToDto();
result.AccessKey = Mask(result.AccessKey);
result.SecretKey = Mask(result.SecretKey);
return Result.Success(result);
}
catch (Exception ex)
{
return Result.Error("新增存储配置失败", ex);
}
}
public async Task> UpdateAsync(FileStorageConfigDto dto)
{
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
return Result.Error("配置Id无效");
if (!string.IsNullOrWhiteSpace(dto.Provider) && !IsProviderSupported(dto.Provider))
return Result.Error($"不支持的 Provider:{dto.Provider}");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("存储配置不存在或已被删除");
// AccessKey/SecretKey 为掩码时保留原值(前端未改密钥)
var newEntity = dto.ToEntity();
newEntity.Id = id;
newEntity.CreateTime = entity.CreateTime;
if (string.IsNullOrEmpty(newEntity.AccessKey) || newEntity.AccessKey.Contains(MaskMarker))
newEntity.AccessKey = entity.AccessKey;
if (string.IsNullOrEmpty(newEntity.SecretKey) || newEntity.SecretKey.Contains(MaskMarker))
newEntity.SecretKey = entity.SecretKey;
if (newEntity.IsDefault == 1) await ClearOtherDefaultAsync(id);
await SqlSugarContext.DbContext.Updateable(newEntity)
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
.ExecuteCommandAsync();
var result = newEntity.ToDto();
result.AccessKey = Mask(result.AccessKey);
result.SecretKey = Mask(result.SecretKey);
return Result.Success(result);
}
catch (Exception ex)
{
return Result.Error("修改存储配置失败", ex);
}
}
public async Task DeleteAsync(long id)
{
if (id <= 0) return Result.Error("配置Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("存储配置不存在或已被删除");
if (entity.IsDefault == 1) return Result.Error($"配置「{entity.Name}」是默认通道,禁止删除(请先切换默认)");
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDel == 1)
.Where(x => x.Id == id).ExecuteCommandAsync();
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("删除存储配置失败", ex);
}
}
public async Task SetDefaultAsync(long id)
{
if (id <= 0) return Result.Error("配置Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("存储配置不存在或已被删除");
if (entity.Status == 0) return Result.Error("已停用的配置不能设为默认");
await ClearOtherDefaultAsync(id);
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDefault == 1)
.Where(x => x.Id == id).ExecuteCommandAsync();
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("设置默认存储失败", ex);
}
}
public async Task TestAsync(long id)
{
if (id <= 0) return Result.Error("配置Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("存储配置不存在或已被删除");
var provider = ResolveProvider(entity.Provider);
if (provider == null) return Result.Error($"未注册 Provider:{entity.Provider}");
return await provider.TestConnectionAsync(entity);
}
catch (Exception ex)
{
return Result.Error("测试连接失败", ex);
}
}
// ==================== 文件上传/记录 ====================
public async Task> UploadAsync(Stream stream, string originalName, long size, string? bizType = null, string? bizId = null, string? uploader = null)
{
if (stream == null || size <= 0) return Result.Error("文件流或大小无效");
if (string.IsNullOrWhiteSpace(originalName)) return Result.Error("文件名不能为空");
try
{
// 取默认启用的存储配置
var config = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.IsDel == 0 && x.IsDefault == 1 && x.Status == 1).FirstAsync();
if (config == null) return Result.Error("未配置默认存储通道,请先在「文件存储管理」新增并设为默认");
// 大小校验
if (config.MaxSizeMB > 0 && size > config.MaxSizeMB * 1024L * 1024L)
return Result.Error($"文件大小超过上限 {config.MaxSizeMB}MB");
// 扩展名校验
var ext = Path.GetExtension(originalName)?.ToLowerInvariant() ?? "";
var allowed = ParseExts(config.AllowedExts);
if (allowed.Count > 0 && !allowed.Contains(ext))
return Result.Error($"不支持的文件类型 {ext}(允许:{string.Join(",", allowed)})");
var provider = ResolveProvider(config.Provider);
if (provider == null) return Result.Error($"未注册 Provider:{config.Provider}");
var upRes = await provider.UploadAsync(stream, originalName, size, config);
if (!upRes.IsSuccess || upRes.Data == null) return Result.Error(upRes.Msg);
var record = new FileRecordEntity
{
FileName = upRes.Data.FileName,
OriginalName = originalName,
Url = upRes.Data.Url,
Size = size,
Ext = ext,
Provider = config.Provider,
StorageId = config.Id,
Uploader = string.IsNullOrWhiteSpace(uploader) ? _currentUser.UserName : uploader,
BizType = string.IsNullOrWhiteSpace(bizType) ? "generic" : bizType,
BizId = bizId,
CreateTime = DateTime.Now
};
var rid = await SqlSugarContext.DbContext.Insertable(record).ExecuteReturnSnowflakeIdAsync();
record.Id = rid;
return Result.Success(record.ToDto());
}
catch (Exception ex)
{
return Result.Error("文件上传失败", ex);
}
}
public async Task>> GetFileListPagedAsync(int pageIndex, int pageSize, RefAsync total, string? keyword = null, string? bizType = null)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.IsDel == 0)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.FileName.Contains(keyword!) || x.OriginalName!.Contains(keyword!))
.WhereIF(!string.IsNullOrWhiteSpace(bizType), x => x.BizType == bizType)
.OrderBy(x => x.CreateTime, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize, total);
return Result>.Success(list.ToDtoList());
}
catch (Exception ex)
{
return Result>.Error("查询文件记录失败", ex);
}
}
public async Task> GetFileByIdAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("文件记录不存在或已被删除");
return Result.Success(entity.ToDto());
}
catch (Exception ex)
{
return Result.Error("查询文件记录失败", ex);
}
}
// ==================== 私有工具 ====================
private IFileStorageProvider? ResolveProvider(string? providerName)
{
if (string.IsNullOrWhiteSpace(providerName)) return null;
return _providers.FirstOrDefault(p => p.ProviderName == providerName);
}
private static bool IsProviderSupported(string provider)
=> provider == "local" || provider == "minio" || provider == "oss";
private static string? Mask(string? value)
{
if (string.IsNullOrWhiteSpace(value)) return value;
if (value.Length <= 4) return MaskMarker;
return value[..2] + MaskMarker + value[^2..];
}
private static List ParseExts(string? json)
{
if (string.IsNullOrWhiteSpace(json)) return new List();
try
{
var arr = System.Text.Json.JsonSerializer.Deserialize>(json);
return arr?.Select(e => e.ToLowerInvariant()).ToList() ?? new List();
}
catch { return new List(); }
}
private async Task ClearOtherDefaultAsync(long keepId)
{
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDefault == 0)
.Where(x => x.IsDel == 0 && x.Id != keepId).ExecuteCommandAsync();
}
}
}