feat: 应用内通知系统 + 结构化手术史/用药等相关改动

- 新增用户通知 outbox 流水线(EfUserNotificationPipeline)与后台投递 worker
- 通知中心页面及前端通知服务接入
- 健康指标异常、用药/运动提醒等事件统一产出站内通知
- 健康档案结构化手术史、用药提醒扫描、医生/用户端点等配套调整
- AppDbContext 注册通知相关实体
This commit is contained in:
MingNian
2026-06-21 21:06:29 +08:00
parent b57d0d16f4
commit 13714d9ed8
34 changed files with 1541 additions and 200 deletions

View File

@@ -1,5 +1,3 @@
using System.Text.Json;
namespace Health.Application.Notifications;
public sealed class InAppNotificationService(IInAppNotificationRepository notifications) : IInAppNotificationService
@@ -12,36 +10,22 @@ public sealed class InAppNotificationService(IInAppNotificationRepository notifi
return records.Select(ToDto).ToList();
}
public async Task<IReadOnlyList<InAppNotificationDto>> GetHistoryAsync(Guid userId, CancellationToken ct)
{
var records = await _notifications.GetHistoryAsync(userId, ct);
return records.Select(ToDto).ToList();
}
public Task<bool> AcknowledgeAsync(Guid userId, Guid notificationId, CancellationToken ct) =>
_notifications.AcknowledgeAsync(userId, notificationId, ct);
private static InAppNotificationDto ToDto(InAppNotificationRecord record)
{
if (record.Type == "MedicationReminder")
{
using var json = JsonDocument.Parse(record.Payload);
var root = json.RootElement;
var name = root.TryGetProperty("Name", out var nameValue) ? nameValue.GetString() : null;
var dosage = root.TryGetProperty("Dosage", out var dosageValue) ? dosageValue.GetString() : null;
var time = root.TryGetProperty("ScheduledTime", out var timeValue) ? timeValue.GetString() : null;
var details = string.Join(" · ", new[] { dosage, time }.Where(x => !string.IsNullOrWhiteSpace(x)));
return new InAppNotificationDto(
record.Id,
record.Type,
"用药提醒",
$"该服用{name ?? ""}{(details.Length > 0 ? $"{details}" : "")}了",
record.CreatedAt);
}
public Task<int> MarkAllReadAsync(Guid userId, CancellationToken ct) =>
_notifications.MarkAllReadAsync(userId, ct);
if (record.Type == "ExerciseReminder")
{
using var json = JsonDocument.Parse(record.Payload);
var root = json.RootElement;
var type = root.TryGetProperty("ExerciseType", out var typeValue) ? typeValue.GetString() : "运动";
var minutes = root.TryGetProperty("DurationMinutes", out var minutesValue) ? minutesValue.GetInt32() : 30;
return new InAppNotificationDto(record.Id, record.Type, "运动提醒", $"今天的{type}计划还未完成,目标 {minutes} 分钟", record.CreatedAt);
}
public Task<bool> DeleteAsync(Guid userId, Guid notificationId, CancellationToken ct) =>
_notifications.DeleteAsync(userId, notificationId, ct);
return new InAppNotificationDto(record.Id, record.Type, "健康提醒", "您有一条新的健康提醒", record.CreatedAt);
}
private static InAppNotificationDto ToDto(InAppNotificationRecord record) => new(
record.Id, record.Type, record.Title, record.Message, record.Severity,
record.ActionType, record.ActionTargetId, record.IsRead, record.CreatedAt);
}