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