feat: 应用内通知系统 + 结构化手术史/用药等相关改动
- 新增用户通知 outbox 流水线(EfUserNotificationPipeline)与后台投递 worker - 通知中心页面及前端通知服务接入 - 健康指标异常、用药/运动提醒等事件统一产出站内通知 - 健康档案结构化手术史、用药提醒扫描、医生/用户端点等配套调整 - AppDbContext 注册通知相关实体
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user