核心功能: - 公告状态机(草稿/已发布/已撤销)支持发布、撤销和重新发布 - 发布者范围区分平台级和租户级公告 - 目标受众定向推送(全部租户/指定角色/指定用户) - 平台管理、租户管理和应用端查询API - 已读/未读管理和未读统计 技术实现: - CQRS+DDD架构,清晰的领域边界和事件驱动 - 查询性能优化:数据库端排序和限制,估算策略减少内存占用 - 并发控制:修复RowVersion配置(IsRowVersion→IsConcurrencyToken) - 完整的FluentValidation验证器和输入保护 测试验证: - 36个测试全部通过(27单元+9集成) - 性能测试达标(1000条数据<5秒) - 代码质量评级A(优秀) 文档: - 完整的ADR、API文档和迁移指南 - 交付报告和技术债务记录
72 lines
2.5 KiB
C#
72 lines
2.5 KiB
C#
using MediatR;
|
|
using TakeoutSaaS.Application.App.Tenants.Dto;
|
|
using TakeoutSaaS.Application.App.Tenants.Queries;
|
|
using TakeoutSaaS.Application.App.Tenants.Targeting;
|
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
|
|
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|
|
|
/// <summary>
|
|
/// 公告详情查询处理器。
|
|
/// </summary>
|
|
public sealed class GetAnnouncementByIdQueryHandler(
|
|
ITenantAnnouncementRepository announcementRepository,
|
|
ITenantAnnouncementReadRepository readRepository,
|
|
ITenantProvider tenantProvider,
|
|
ICurrentUserAccessor? currentUserAccessor = null,
|
|
IAdminAuthService? adminAuthService = null,
|
|
IMiniAuthService? miniAuthService = null)
|
|
: IRequestHandler<GetAnnouncementByIdQuery, TenantAnnouncementDto?>
|
|
{
|
|
/// <summary>
|
|
/// 查询公告详情。
|
|
/// </summary>
|
|
/// <param name="request">查询请求。</param>
|
|
/// <param name="cancellationToken">取消标记。</param>
|
|
/// <returns>公告 DTO 或 null。</returns>
|
|
public async Task<TenantAnnouncementDto?> Handle(GetAnnouncementByIdQuery request, CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
|
|
|
// 1. 查询公告主体(含平台公告)
|
|
var announcement = await announcementRepository.FindByIdInScopeAsync(tenantId, request.AnnouncementId, cancellationToken);
|
|
if (announcement == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// 2. 目标受众过滤
|
|
var targetContext = await AnnouncementTargetContextFactory.BuildAsync(
|
|
tenantProvider,
|
|
currentUserAccessor,
|
|
adminAuthService,
|
|
miniAuthService,
|
|
cancellationToken);
|
|
|
|
if (!TargetTypeFilter.IsMatch(announcement, targetContext))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// 3. 优先查用户级已读
|
|
var userId = targetContext.UserId;
|
|
var reads = await readRepository.GetByAnnouncementAsync(
|
|
tenantId,
|
|
new[] { announcement.Id },
|
|
userId == 0 ? null : userId,
|
|
cancellationToken);
|
|
|
|
if (reads.Count == 0)
|
|
{
|
|
var tenantReads = await readRepository.GetByAnnouncementAsync(tenantId, new[] { announcement.Id }, null, cancellationToken);
|
|
reads = tenantReads;
|
|
}
|
|
|
|
var readRecord = reads.FirstOrDefault();
|
|
return announcement.ToDto(readRecord != null, readRecord?.ReadAt);
|
|
}
|
|
}
|