chore: 优化公告已读批量与用户维度
This commit is contained in:
@@ -2,6 +2,7 @@ using MediatR;
|
|||||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Queries;
|
using TakeoutSaaS.Application.App.Tenants.Queries;
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||||
|
|
||||||
@@ -10,7 +11,8 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GetTenantAnnouncementQueryHandler(
|
public sealed class GetTenantAnnouncementQueryHandler(
|
||||||
ITenantAnnouncementRepository announcementRepository,
|
ITenantAnnouncementRepository announcementRepository,
|
||||||
ITenantAnnouncementReadRepository readRepository)
|
ITenantAnnouncementReadRepository readRepository,
|
||||||
|
ICurrentUserAccessor? currentUserAccessor = null)
|
||||||
: IRequestHandler<GetTenantAnnouncementQuery, TenantAnnouncementDto?>
|
: IRequestHandler<GetTenantAnnouncementQuery, TenantAnnouncementDto?>
|
||||||
{
|
{
|
||||||
public async Task<TenantAnnouncementDto?> Handle(GetTenantAnnouncementQuery request, CancellationToken cancellationToken)
|
public async Task<TenantAnnouncementDto?> Handle(GetTenantAnnouncementQuery request, CancellationToken cancellationToken)
|
||||||
@@ -21,7 +23,20 @@ public sealed class GetTenantAnnouncementQueryHandler(
|
|||||||
return null;
|
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();
|
var readRecord = reads.FirstOrDefault();
|
||||||
return announcement.ToDto(readRecord != null, readRecord?.ReadAt);
|
return announcement.ToDto(readRecord != null, readRecord?.ReadAt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||||
using TakeoutSaaS.Application.App.Tenants.Queries;
|
using TakeoutSaaS.Application.App.Tenants.Queries;
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||||
|
|
||||||
@@ -12,7 +14,8 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SearchTenantAnnouncementsQueryHandler(
|
public sealed class SearchTenantAnnouncementsQueryHandler(
|
||||||
ITenantAnnouncementRepository announcementRepository,
|
ITenantAnnouncementRepository announcementRepository,
|
||||||
ITenantAnnouncementReadRepository announcementReadRepository)
|
ITenantAnnouncementReadRepository announcementReadRepository,
|
||||||
|
ICurrentUserAccessor? currentUserAccessor = null)
|
||||||
: IRequestHandler<SearchTenantAnnouncementsQuery, PagedResult<TenantAnnouncementDto>>
|
: IRequestHandler<SearchTenantAnnouncementsQuery, PagedResult<TenantAnnouncementDto>>
|
||||||
{
|
{
|
||||||
public async Task<PagedResult<TenantAnnouncementDto>> Handle(SearchTenantAnnouncementsQuery request, CancellationToken cancellationToken)
|
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 effectiveAt = request.OnlyEffective == true ? DateTime.UtcNow : (DateTime?)null;
|
||||||
var announcements = await announcementRepository.SearchAsync(request.TenantId, request.AnnouncementType, request.IsActive, effectiveAt, cancellationToken);
|
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
|
var ordered = announcements
|
||||||
.OrderByDescending(x => x.Priority)
|
.OrderByDescending(x => x.Priority)
|
||||||
.ThenByDescending(x => x.CreatedAt)
|
.ThenByDescending(x => x.CreatedAt)
|
||||||
@@ -39,9 +31,41 @@ public sealed class SearchTenantAnnouncementsQueryHandler(
|
|||||||
var page = request.Page <= 0 ? 1 : request.Page;
|
var page = request.Page <= 0 ? 1 : request.Page;
|
||||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||||
|
|
||||||
var items = ordered
|
var pageItems = ordered
|
||||||
.Skip((page - 1) * size)
|
.Skip((page - 1) * size)
|
||||||
.Take(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 =>
|
.Select(a =>
|
||||||
{
|
{
|
||||||
readMap.TryGetValue(a.Id, out var read);
|
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, 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<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task AddAsync(TenantAnnouncementRead record, 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);
|
.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)
|
public Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return context.TenantAnnouncementReads
|
return context.TenantAnnouncementReads
|
||||||
|
|||||||
Reference in New Issue
Block a user