chore: 优化公告已读批量与用户维度
This commit is contained in:
@@ -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;
|
||||
/// </summary>
|
||||
public sealed class GetTenantAnnouncementQueryHandler(
|
||||
ITenantAnnouncementRepository announcementRepository,
|
||||
ITenantAnnouncementReadRepository readRepository)
|
||||
ITenantAnnouncementReadRepository readRepository,
|
||||
ICurrentUserAccessor? currentUserAccessor = null)
|
||||
: IRequestHandler<GetTenantAnnouncementQuery, TenantAnnouncementDto?>
|
||||
{
|
||||
public async Task<TenantAnnouncementDto?> 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
public sealed class SearchTenantAnnouncementsQueryHandler(
|
||||
ITenantAnnouncementRepository announcementRepository,
|
||||
ITenantAnnouncementReadRepository announcementReadRepository)
|
||||
ITenantAnnouncementReadRepository announcementReadRepository,
|
||||
ICurrentUserAccessor? currentUserAccessor = null)
|
||||
: IRequestHandler<SearchTenantAnnouncementsQuery, PagedResult<TenantAnnouncementDto>>
|
||||
{
|
||||
public async Task<PagedResult<TenantAnnouncementDto>> 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<long, (bool isRead, DateTime? readAt)>();
|
||||
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<long, (bool isRead, DateTime? readAt)>();
|
||||
if (announcementIds.Length > 0)
|
||||
{
|
||||
// 优先查询当前用户维度的已读,其次租户级已读(UserId null)
|
||||
var reads = new List<Domain.Tenants.Entities.TenantAnnouncementRead>();
|
||||
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);
|
||||
|
||||
@@ -12,6 +12,8 @@ public interface ITenantAnnouncementReadRepository
|
||||
{
|
||||
Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, IEnumerable<long> announcementIds, long? userId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -20,6 +20,32 @@ public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext conte
|
||||
.ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, IEnumerable<long> announcementIds, long? userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ids = announcementIds.Distinct().ToArray();
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<TenantAnnouncementRead>>(Array.Empty<TenantAnnouncementRead>());
|
||||
}
|
||||
|
||||
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<TenantAnnouncementRead>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncementReads
|
||||
|
||||
Reference in New Issue
Block a user