feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
92
backend/src/Health.Application/Calendars/CalendarService.cs
Normal file
92
backend/src/Health.Application/Calendars/CalendarService.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
namespace Health.Application.Calendars;
|
||||
|
||||
public sealed class CalendarService(ICalendarRepository calendar) : ICalendarService
|
||||
{
|
||||
private readonly ICalendarRepository _calendar = calendar;
|
||||
|
||||
public async Task<IReadOnlyList<object>> GetMonthAsync(Guid userId, int year, int month, CancellationToken ct)
|
||||
{
|
||||
var start = new DateOnly(year, month, 1);
|
||||
var end = start.AddMonths(1);
|
||||
var snapshot = await _calendar.GetSnapshotAsync(userId, start, end, ct);
|
||||
var result = new List<object>();
|
||||
|
||||
for (var date = start; date < end; date = date.AddDays(1))
|
||||
{
|
||||
var entries = BuildEntries(snapshot, date);
|
||||
if (entries.Count == 0) continue;
|
||||
|
||||
result.Add(new
|
||||
{
|
||||
date = date.ToString("yyyy-MM-dd"),
|
||||
events = entries.Select(entry => entry.Type).Distinct().ToList(),
|
||||
details = entries.Select(entry => entry.Value).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<object> GetDayAsync(Guid userId, DateOnly date, CancellationToken ct)
|
||||
{
|
||||
var snapshot = await _calendar.GetSnapshotAsync(userId, date, date.AddDays(1), ct);
|
||||
var medications = snapshot.Medications
|
||||
.Where(m => IsMedicationActiveOn(m, date))
|
||||
.Select(m => new { m.Name, m.Dosage, timeOfDay = m.TimeOfDay.Select(t => t.ToString(@"hh\:mm")).ToList() })
|
||||
.ToList();
|
||||
var exercises = snapshot.ExercisePlans
|
||||
.SelectMany(p => p.Items)
|
||||
.Where(i => i.ScheduledDate == date && !i.IsRestDay)
|
||||
.Select(i => new { type = i.ExerciseType, duration = i.DurationMinutes, isCompleted = i.IsCompleted, scheduledDate = i.ScheduledDate })
|
||||
.ToList();
|
||||
var followUps = snapshot.FollowUps
|
||||
.Where(f => DateOnly.FromDateTime(f.ScheduledAt) == date)
|
||||
.Select(f => new { f.Title, f.DoctorName, f.Department, status = f.Status.ToString() })
|
||||
.ToList();
|
||||
|
||||
return new { medications, exercises, followUps };
|
||||
}
|
||||
|
||||
private static List<CalendarEntry> BuildEntries(CalendarDataSnapshot snapshot, DateOnly date)
|
||||
{
|
||||
var entries = new List<CalendarEntry>();
|
||||
foreach (var medication in snapshot.Medications.Where(m => IsMedicationActiveOn(m, date)))
|
||||
{
|
||||
entries.Add(new CalendarEntry("medication", new
|
||||
{
|
||||
type = "medication",
|
||||
name = medication.Name,
|
||||
dosage = medication.Dosage,
|
||||
timeOfDay = medication.TimeOfDay.Select(t => t.ToString(@"hh\:mm")).ToList()
|
||||
}));
|
||||
}
|
||||
|
||||
foreach (var plan in snapshot.ExercisePlans)
|
||||
{
|
||||
foreach (var item in plan.Items.Where(i => i.ScheduledDate == date && !i.IsRestDay))
|
||||
entries.Add(new CalendarEntry("exercise", new { type = "exercise", name = item.ExerciseType, duration = item.DurationMinutes, isCompleted = item.IsCompleted, scheduledDate = item.ScheduledDate }));
|
||||
}
|
||||
|
||||
foreach (var followUp in snapshot.FollowUps.Where(f => DateOnly.FromDateTime(f.ScheduledAt) == date))
|
||||
{
|
||||
entries.Add(new CalendarEntry("followup", new
|
||||
{
|
||||
type = "followup",
|
||||
title = followUp.Title,
|
||||
doctorName = followUp.DoctorName,
|
||||
department = followUp.Department,
|
||||
status = followUp.Status.ToString()
|
||||
}));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool IsMedicationActiveOn(Health.Domain.Entities.Medication medication, DateOnly date) =>
|
||||
medication.IsActive
|
||||
&& medication.TimeOfDay.Count > 0
|
||||
&& (medication.StartDate == null || medication.StartDate <= date)
|
||||
&& (medication.EndDate == null || medication.EndDate >= date);
|
||||
|
||||
private sealed record CalendarEntry(string Type, object Value);
|
||||
}
|
||||
Reference in New Issue
Block a user