Билд
This commit is contained in:
@@ -14,9 +14,9 @@ public static class MongoDbMapConfigurator
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
|
||||
try { BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard)); } catch { /* Already registered */ }
|
||||
|
||||
BsonSerializer.RegisterSerializer(new EnumSerializer<MessageState>(BsonType.String));
|
||||
BsonSerializer.RegisterSerializer(new EnumSerializer<MessageState>(BsonType.Int32));
|
||||
BsonSerializer.RegisterSerializer(new EnumSerializer<MediaType>(BsonType.String));
|
||||
|
||||
BsonClassMap.RegisterClassMap<Entity<Guid>>(cm =>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Messaging.Infrastructure.Persistence.Mongo;
|
||||
|
||||
public sealed class UserStatsService : IUserStatsService
|
||||
{
|
||||
private readonly IMongoCollection<Message> _messages;
|
||||
|
||||
public UserStatsService(IMongoDatabase database)
|
||||
{
|
||||
_messages = database.GetCollection<Message>("messages");
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, UserStats>> GetStatsForUsersAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
|
||||
{
|
||||
var userGuidList = userIds.ToList();
|
||||
if (!userGuidList.Any()) return new Dictionary<Guid, UserStats>();
|
||||
|
||||
// Эффективная агрегация: считаем количество и сумму Media.Size
|
||||
var stats = await _messages.Aggregate()
|
||||
.Match(Builders<Message>.Filter.In(m => m.SenderId, userGuidList))
|
||||
.Group(new BsonDocument {
|
||||
{ "_id", "$SenderId" },
|
||||
{ "Count", new BsonDocument("$sum", 1) },
|
||||
{ "MediaSize", new BsonDocument("$sum", new BsonDocument("$sum", "$Media.Size")) }
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
return stats.ToDictionary(
|
||||
doc => doc["_id"].AsGuid,
|
||||
doc => new UserStats(
|
||||
doc["Count"].AsInt32,
|
||||
doc.Contains("MediaSize") && !doc["MediaSize"].IsBsonNull ? (long)(doc["MediaSize"].IsInt64 ? doc["MediaSize"].AsInt64 : doc["MediaSize"].AsInt32) : 0L
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<long> GetTotalStorageSizeAsync(CancellationToken ct = default)
|
||||
{
|
||||
var result = await _messages.Aggregate()
|
||||
.Group(new BsonDocument {
|
||||
{ "_id", BsonNull.Value },
|
||||
{ "TotalSize", new BsonDocument("$sum", new BsonDocument("$sum", "$Media.Size")) }
|
||||
})
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (result == null) return 0;
|
||||
return result.Contains("TotalSize") ? (long)(result["TotalSize"].IsInt64 ? result["TotalSize"].AsInt64 : result["TotalSize"].AsInt32) : 0L;
|
||||
}
|
||||
|
||||
public async Task<int> GetCountOrphanedMessagesAsync(HashSet<Guid> activeChatIds, CancellationToken ct = default)
|
||||
{
|
||||
var filter = Builders<Message>.Filter.Or(
|
||||
Builders<Message>.Filter.BitsAnySet(m => m.State, (long)MessageState.IsDeleted),
|
||||
Builders<Message>.Filter.Nin(m => m.ChatId, activeChatIds)
|
||||
);
|
||||
return (int)(await _messages.CountDocumentsAsync(filter, cancellationToken: ct));
|
||||
}
|
||||
|
||||
public async Task<long> GetOrphanedMediaSizeAsync(HashSet<string> validFileIds, CancellationToken ct = default)
|
||||
{
|
||||
// Для больших объемов правильнее собирать список ВСЕХ URL файлов из сообщений,
|
||||
// но здесь мы оптимизируем через проекцию, чтобы вернуть только нужные поля.
|
||||
// Этот метод может быть реализован в BackgroundTask для очень больших баз.
|
||||
return 0; // Временная заглушка, реальный подсчет через курсор в DryRun
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user