feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化

- 核心业务拆分为 Endpoint → Application Service → Repository 三层
- AI写入操作必须用户确认后才写库(确认卡片机制)
- 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复)
- 运动计划修复: 连续真实日期替代周模板
- 用药提醒去重 + 通知Outbox预留
- 认证收拢到AuthService, 管理员收拢到AdminService
- AI会话加用户归属校验防串号
- 提示词调整为患者视角
- 开发假数据已关闭
- 21/21测试通过, 0警告0错误
This commit is contained in:
MingNian
2026-06-20 20:41:42 +08:00
parent c610417e29
commit 4d213b5a44
132 changed files with 6733 additions and 2856 deletions

View File

@@ -1,22 +1,31 @@
using Health.Application.Medications;
namespace Health.WebApi.BackgroundServices;
/// <summary>
/// 用药提醒定时扫描服务(每分钟检查一次)
/// </summary>
public sealed class MedicationReminderService(IServiceScopeFactory scopeFactory, ILogger<MedicationReminderService> logger) : BackgroundService
public sealed class MedicationReminderService(
IServiceScopeFactory scopeFactory,
ILogger<MedicationReminderService> logger) : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
private readonly ILogger<MedicationReminderService> _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("用药提醒服务已启动");
_logger.LogInformation("用药提醒扫描生产者已启动");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await ProcessReminders(stoppingToken);
using var scope = _scopeFactory.CreateScope();
var scanner = scope.ServiceProvider.GetRequiredService<IMedicationReminderScanner>();
var queue = scope.ServiceProvider.GetRequiredService<IMedicationReminderQueue>();
var tasks = await scanner.ScanAsync(DateTime.UtcNow, stoppingToken);
foreach (var task in tasks)
await queue.EnqueueAsync(task, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
@@ -26,37 +35,4 @@ public sealed class MedicationReminderService(IServiceScopeFactory scopeFactory,
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
private async Task ProcessReminders(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 北京时间今天0点 → 转为UTC
var beijingToday = DateTime.UtcNow.AddHours(8).Date;
var todayStartUtc = beijingToday.AddHours(-8);
// 查询:启用的用药计划 AND 服药时间在当前时间前后5分钟窗口内
var beijingTime = TimeOnly.FromDateTime(DateTime.UtcNow.AddHours(8));
var windowStart = beijingTime.AddMinutes(-5);
var medications = await db.Medications
.Where(m => m.IsActive)
.Where(m => m.TimeOfDay.Any(t =>
beijingTime > windowStart ? (t >= windowStart && t <= beijingTime) : (t >= windowStart || t <= beijingTime)))
.ToListAsync(ct);
foreach (var med in medications)
{
// 检查今天是否已打卡(用正确的北京时间区间)
var alreadyLogged = await db.MedicationLogs
.AnyAsync(l => l.MedicationId == med.Id
&& l.CreatedAt >= todayStartUtc
&& l.Status == MedicationLogStatus.Taken, ct);
if (alreadyLogged) continue;
// TODO: 调用极光推送发送用药提醒
_logger.LogInformation("用药提醒: 用户 {UserId} 药品 {Name} {Dosage} 时间 {Time}",
med.UserId, med.Name, med.Dosage, beijingTime);
}
}
}