using System.Text.Json; using Microsoft.EntityFrameworkCore; using Tiku.Application.Assets; using Tiku.Application.Catalog; using Tiku.Application.Content; using Tiku.Domain.Content; namespace Tiku.Infrastructure.Assets; internal sealed class AssetCatalogManagementService(AssetManagementDependencies dependencies) : AssetManagementServiceBase(dependencies), IAssetCatalogManagementService { public async Task> GetAssetsAsync( AssetManagementActor actor, AssetManagementFilter filter, CancellationToken cancellationToken = default) { var query = contentAssetPersistence.ContentAssets .AsNoTracking() .Where(asset => asset.TenantId == actor.TenantId); if (filter.RegionId.HasValue) query = query.Where(asset => asset.RegionId == filter.RegionId.Value); if (filter.SubjectId.HasValue) query = query.Where(asset => asset.SubjectId == filter.SubjectId.Value); if (filter.CategoryId.HasValue) query = query.Where(asset => asset.CategoryId == filter.CategoryId.Value); if (filter.ContentNodeId.HasValue) query = query.Where(asset => asset.ContentNodeId == filter.ContentNodeId.Value); if (!string.IsNullOrWhiteSpace(filter.AssetType) && Enum.TryParse(filter.AssetType, true, out var assetType)) query = query.Where(asset => asset.AssetType == assetType); if (!string.IsNullOrWhiteSpace(filter.UploadStatus) && Enum.TryParse(filter.UploadStatus, true, out var uploadStatus)) query = query.Where(asset => asset.UploadStatus == uploadStatus); if (!string.IsNullOrWhiteSpace(filter.SecurityScanStatus) && Enum.TryParse(filter.SecurityScanStatus, true, out var securityScanStatus)) query = query.Where(asset => asset.SecurityScanStatus == securityScanStatus); if (!string.IsNullOrWhiteSpace(filter.Category)) { var category = filter.Category.Trim(); query = query.Where(asset => asset.Category == category); } if (!string.IsNullOrWhiteSpace(filter.Keyword)) { var keyword = filter.Keyword.Trim(); query = query.Where(asset => (asset.Title != null && asset.Title.Contains(keyword)) || (asset.FileName != null && asset.FileName.Contains(keyword)) || (asset.Description != null && asset.Description.Contains(keyword)) || (asset.AssetKey != null && asset.AssetKey.Contains(keyword))); } var items = await query .OrderBy(asset => asset.SortOrder) .ThenByDescending(asset => asset.CreatedAt) .Take(ResolveLimit(filter.Limit)) .Select(asset => ToItem(asset)) .ToArrayAsync(cancellationToken); return new CatalogList(items); } public async Task> UpsertAssetAsync( AssetManagementActor actor, UpsertAssetCommand command, CancellationToken cancellationToken = default) { var asset = await ResolveManagementAssetAsync(actor, command, cancellationToken); var accountedBytesBefore = AccountedStorageBytes(asset); asset.RegionId = command.RegionId; asset.SubjectId = command.SubjectId; asset.CategoryId = command.CategoryId; asset.ContentNodeId = command.ContentNodeId; asset.LegacyId = NormalizeOptional(command.LegacyId); asset.AssetKey = NormalizeOptional(command.AssetKey); asset.Title = NormalizeOptional(command.Title) ?? NormalizeOptional(command.FileName) ?? asset.Title ?? "未命名资源"; asset.Category = NormalizeOptional(command.Category); asset.Description = NormalizeOptional(command.Description); asset.FileName = NormalizeOptional(command.FileName); asset.CdnUrl = NormalizeOptional(command.CdnUrl); asset.IsPublic = command.IsPublic ?? asset.IsPublic; asset.AssetType = ParseEnum(command.AssetType, asset.AssetType); asset.Visibility = ResolveVisibility(command.Visibility, asset.IsPublic); asset.Status = ParseEnum(command.Status, asset.Status); asset.StorageProvider = ToAssetStorageProvider(objectStorageService.NormalizeProvider(command.Provider, ToObjectStorageProvider(asset.StorageProvider))); asset.Bucket = string.IsNullOrWhiteSpace(command.Bucket) ? asset.Bucket : command.Bucket.Trim(); asset.ObjectKey = string.IsNullOrWhiteSpace(command.ObjectKey) ? asset.ObjectKey : objectStorageService.ValidateObjectKey(actor.TenantId, command.ObjectKey.Trim()); asset.MimeType = string.IsNullOrWhiteSpace(command.MimeType) ? asset.MimeType : objectStorageService.ValidateMimeType(command.MimeType.Trim()); asset.FileSizeBytes = objectStorageService.ValidateFileSize(command.FileSizeBytes ?? asset.FileSizeBytes); asset.ChecksumSha256 = NormalizeChecksum(command.ChecksumSha256) ?? asset.ChecksumSha256; asset.PreviewUrl = NormalizeOptional(command.PreviewUrl); asset.PreviewObjectKey = NormalizeOptional(command.PreviewObjectKey) ?? asset.PreviewObjectKey; asset.SortOrder = command.Order ?? asset.SortOrder; asset.AccessRules = command.AccessRules.ValueKind == JsonValueKind.Undefined ? asset.AccessRules : command.AccessRules; asset.Metadata = command.Metadata.ValueKind == JsonValueKind.Undefined ? asset.Metadata : command.Metadata; asset.UpdatedBy = actor.UserId; var accountedBytesAfter = AccountedStorageBytes(asset); await SaveWithStorageQuotaAdjustmentAsync( actor.TenantId, accountedBytesAfter - accountedBytesBefore, cancellationToken); return new ContentManagementResult(ToItem(asset)); } }