feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user