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

@@ -132,6 +132,7 @@ public static class DoctorEndpoints
var user = await db.Users
.Include(u => u.HealthArchive)
.ThenInclude(a => a!.Surgeries)
.FirstOrDefaultAsync(u => u.Id == id && u.DoctorId == doctorId);
if (user == null)
return Results.Ok(new { code = 404, data = (object?)null, message = "患者不存在" });
@@ -179,7 +180,18 @@ public static class DoctorEndpoints
data = new
{
profile = new { user.Id, user.Phone, user.Name, user.Gender, BirthDate = user.BirthDate?.ToString("yyyy-MM-dd"), user.AvatarUrl },
archive = user.HealthArchive == null ? null : new { user.HealthArchive.Diagnosis, user.HealthArchive.SurgeryType, SurgeryDate = user.HealthArchive.SurgeryDate?.ToString("yyyy-MM-dd"), user.HealthArchive.Allergies, user.HealthArchive.DietRestrictions, user.HealthArchive.ChronicDiseases, user.HealthArchive.FamilyHistory },
archive = user.HealthArchive == null ? null : new
{
user.HealthArchive.Diagnosis,
user.HealthArchive.SurgeryType,
SurgeryDate = user.HealthArchive.SurgeryDate?.ToString("yyyy-MM-dd"),
surgeries = user.HealthArchive.Surgeries.OrderBy(s => s.SortOrder)
.Select(s => new { s.Type, Date = s.Date.HasValue ? s.Date.Value.ToString("yyyy-MM-dd") : null }),
user.HealthArchive.Allergies,
user.HealthArchive.DietRestrictions,
user.HealthArchive.ChronicDiseases,
user.HealthArchive.FamilyHistory
},
latestRecords,
trendRecords,
medications,

View File

@@ -17,6 +17,19 @@ public static class NotificationEndpoints
return Results.Ok(new { code = 0, data = result, message = (string?)null });
});
group.MapGet("", async (HttpContext http, IInAppNotificationService notifications, CancellationToken ct) =>
{
var userId = GetUserId(http);
if (userId == Guid.Empty) return Results.Unauthorized();
var result = await notifications.GetHistoryAsync(userId, ct);
return Results.Ok(new
{
code = 0,
data = new { unreadCount = result.Count(x => !x.IsRead), items = result },
message = (string?)null
});
});
group.MapPost("/{id:guid}/acknowledge", async (Guid id, HttpContext http, IInAppNotificationService notifications, CancellationToken ct) =>
{
var userId = GetUserId(http);
@@ -24,6 +37,25 @@ public static class NotificationEndpoints
var acknowledged = await notifications.AcknowledgeAsync(userId, id, ct);
return Results.Ok(new { code = 0, data = new { acknowledged }, message = (string?)null });
});
group.MapPost("/read-all", async (HttpContext http, IInAppNotificationService notifications, CancellationToken ct) =>
{
var userId = GetUserId(http);
if (userId == Guid.Empty) return Results.Unauthorized();
var count = await notifications.MarkAllReadAsync(userId, ct);
return Results.Ok(new { code = 0, data = new { count }, message = (string?)null });
});
group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, IInAppNotificationService notifications, CancellationToken ct) =>
{
var userId = GetUserId(http);
if (userId == Guid.Empty) return Results.Unauthorized();
var deleted = await notifications.DeleteAsync(userId, id, ct);
return deleted
? Results.Ok(new { code = 0, data = new { deleted = true }, message = (string?)null })
: Results.NotFound(new { code = 404, data = (object?)null, message = "通知不存在" });
});
}
private static Guid GetUserId(HttpContext http) =>

View File

@@ -36,6 +36,9 @@ public static class UserEndpoints
group.MapPut("/health-archive", async (UpdateArchiveRequest req, HttpContext http, IHealthArchiveService archives, CancellationToken ct) =>
{
var surgeryDate = DateOnly.TryParse(req.SurgeryDate, out var parsed) ? parsed : (DateOnly?)null;
var surgeries = req.Surgeries?.Select(s => new HealthArchiveSurgeryInput(
s.Type,
DateOnly.TryParse(s.Date, out var date) ? date : null)).ToList();
await archives.UpdateAsync(
GetUserId(http),
new HealthArchiveUpdateRequest(
@@ -45,7 +48,8 @@ public static class UserEndpoints
req.Allergies,
req.DietRestrictions,
req.ChronicDiseases,
req.FamilyHistory),
req.FamilyHistory,
surgeries),
ct);
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
});
@@ -65,4 +69,7 @@ public sealed record UpdateProfileRequest(string? Name, string? Gender, string?
public sealed record UpdateArchiveRequest(
string? Diagnosis, string? SurgeryType, string? SurgeryDate,
List<string>? Allergies, List<string>? DietRestrictions,
List<string>? ChronicDiseases, string? FamilyHistory);
List<string>? ChronicDiseases, string? FamilyHistory,
List<UpdateArchiveSurgeryRequest>? Surgeries);
public sealed record UpdateArchiveSurgeryRequest(string Type, string? Date);