From a3dc5f54e5605d916b82412124030b9eb71c2ac5 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Wed, 3 Dec 2025 21:13:52 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=BC=98=E5=8C=96=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E5=B7=B2=E8=AF=BB=E6=89=B9=E9=87=8F=E4=B8=8E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BB=B4=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetTenantAnnouncementQueryHandler.cs | 19 ++++++- .../SearchTenantAnnouncementsQueryHandler.cs | 50 ++++++++++++++----- .../ITenantAnnouncementReadRepository.cs | 2 + .../EfTenantAnnouncementReadRepository.cs | 26 ++++++++++ 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantAnnouncementQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantAnnouncementQueryHandler.cs index bfc8f87..70c6201 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantAnnouncementQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantAnnouncementQueryHandler.cs @@ -2,6 +2,7 @@ using MediatR; using TakeoutSaaS.Application.App.Tenants.Dto; using TakeoutSaaS.Application.App.Tenants.Queries; using TakeoutSaaS.Domain.Tenants.Repositories; +using TakeoutSaaS.Shared.Abstractions.Security; namespace TakeoutSaaS.Application.App.Tenants.Handlers; @@ -10,7 +11,8 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// public sealed class GetTenantAnnouncementQueryHandler( ITenantAnnouncementRepository announcementRepository, - ITenantAnnouncementReadRepository readRepository) + ITenantAnnouncementReadRepository readRepository, + ICurrentUserAccessor? currentUserAccessor = null) : IRequestHandler { public async Task Handle(GetTenantAnnouncementQuery request, CancellationToken cancellationToken) @@ -21,7 +23,20 @@ public sealed class GetTenantAnnouncementQueryHandler( return null; } - var reads = await readRepository.GetByAnnouncementAsync(request.TenantId, request.AnnouncementId, cancellationToken); + var userId = currentUserAccessor?.UserId ?? 0; + var reads = await readRepository.GetByAnnouncementAsync( + request.TenantId, + new[] { request.AnnouncementId }, + userId == 0 ? null : userId, + cancellationToken); + + // 如无用户级已读,再查租户级已读 + if (reads.Count == 0) + { + var tenantReads = await readRepository.GetByAnnouncementAsync(request.TenantId, new[] { request.AnnouncementId }, null, cancellationToken); + reads = tenantReads; + } + var readRecord = reads.FirstOrDefault(); return announcement.ToDto(readRecord != null, readRecord?.ReadAt); } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantAnnouncementsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantAnnouncementsQueryHandler.cs index ee5f689..1734531 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantAnnouncementsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantAnnouncementsQueryHandler.cs @@ -1,9 +1,11 @@ +using System.Collections.Generic; using System.Linq; using MediatR; using TakeoutSaaS.Application.App.Tenants.Dto; using TakeoutSaaS.Application.App.Tenants.Queries; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Abstractions.Security; namespace TakeoutSaaS.Application.App.Tenants.Handlers; @@ -12,7 +14,8 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// public sealed class SearchTenantAnnouncementsQueryHandler( ITenantAnnouncementRepository announcementRepository, - ITenantAnnouncementReadRepository announcementReadRepository) + ITenantAnnouncementReadRepository announcementReadRepository, + ICurrentUserAccessor? currentUserAccessor = null) : IRequestHandler> { public async Task> Handle(SearchTenantAnnouncementsQuery request, CancellationToken cancellationToken) @@ -20,17 +23,6 @@ public sealed class SearchTenantAnnouncementsQueryHandler( var effectiveAt = request.OnlyEffective == true ? DateTime.UtcNow : (DateTime?)null; var announcements = await announcementRepository.SearchAsync(request.TenantId, request.AnnouncementType, request.IsActive, effectiveAt, cancellationToken); - var readMap = new Dictionary(); - foreach (var announcement in announcements) - { - var reads = await announcementReadRepository.GetByAnnouncementAsync(request.TenantId, announcement.Id, cancellationToken); - var readRecord = reads.FirstOrDefault(); - if (readRecord != null) - { - readMap[announcement.Id] = (true, readRecord.ReadAt); - } - } - var ordered = announcements .OrderByDescending(x => x.Priority) .ThenByDescending(x => x.CreatedAt) @@ -39,9 +31,41 @@ public sealed class SearchTenantAnnouncementsQueryHandler( var page = request.Page <= 0 ? 1 : request.Page; var size = request.PageSize <= 0 ? 20 : request.PageSize; - var items = ordered + var pageItems = ordered .Skip((page - 1) * size) .Take(size) + .ToList(); + + var announcementIds = pageItems.Select(x => x.Id).ToArray(); + var userId = currentUserAccessor?.UserId ?? 0; + + var readMap = new Dictionary(); + if (announcementIds.Length > 0) + { + // 优先查询当前用户维度的已读,其次租户级已读(UserId null) + var reads = new List(); + if (userId != 0) + { + var userReads = await announcementReadRepository.GetByAnnouncementAsync(request.TenantId, announcementIds, userId, cancellationToken); + reads.AddRange(userReads); + } + + var tenantReads = await announcementReadRepository.GetByAnnouncementAsync(request.TenantId, announcementIds, null, cancellationToken); + reads.AddRange(tenantReads); + + foreach (var read in reads.OrderByDescending(x => x.ReadAt)) + { + // 若已存在用户级标记,跳过租户级覆盖 + if (readMap.ContainsKey(read.AnnouncementId) && read.UserId.HasValue) + { + continue; + } + + readMap[read.AnnouncementId] = (true, read.ReadAt); + } + } + + var items = pageItems .Select(a => { readMap.TryGetValue(a.Id, out var read); diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantAnnouncementReadRepository.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantAnnouncementReadRepository.cs index 86a758b..5265ff2 100644 --- a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantAnnouncementReadRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantAnnouncementReadRepository.cs @@ -12,6 +12,8 @@ public interface ITenantAnnouncementReadRepository { Task> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default); + Task> GetByAnnouncementAsync(long tenantId, IEnumerable announcementIds, long? userId, CancellationToken cancellationToken = default); + Task FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default); Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantAnnouncementReadRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantAnnouncementReadRepository.cs index ebc63ea..96a1fd3 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantAnnouncementReadRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantAnnouncementReadRepository.cs @@ -20,6 +20,32 @@ public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext conte .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); } + public Task> GetByAnnouncementAsync(long tenantId, IEnumerable announcementIds, long? userId, CancellationToken cancellationToken = default) + { + var ids = announcementIds.Distinct().ToArray(); + if (ids.Length == 0) + { + return Task.FromResult>(Array.Empty()); + } + + var query = context.TenantAnnouncementReads.AsNoTracking() + .Where(x => x.TenantId == tenantId && ids.Contains(x.AnnouncementId)); + + if (userId.HasValue) + { + query = query.Where(x => x.UserId == userId.Value); + } + else + { + query = query.Where(x => x.UserId == null); + } + + return query + .OrderByDescending(x => x.ReadAt) + .ToListAsync(cancellationToken) + .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); + } + public Task FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default) { return context.TenantAnnouncementReads