feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
|
||||
namespace Health.Application.HealthRecords;
|
||||
|
||||
public sealed class HealthRecordService(IHealthRecordRepository records) : IHealthRecordService
|
||||
{
|
||||
private readonly IHealthRecordRepository _records = records;
|
||||
|
||||
public async Task<IReadOnlyList<HealthRecordDto>> GetRecordsAsync(Guid userId, string? type, int? days, CancellationToken ct)
|
||||
{
|
||||
HealthMetricType? metricType = null;
|
||||
if (!string.IsNullOrEmpty(type) && Enum.TryParse<HealthMetricType>(type, ignoreCase: true, out var parsed))
|
||||
metricType = parsed;
|
||||
|
||||
var recordedAfter = days.HasValue ? DateTime.UtcNow.AddDays(-days.Value) : (DateTime?)null;
|
||||
var result = await _records.ListAsync(userId, metricType, recordedAfter, 100, ct);
|
||||
return result.Select(ToDto).ToList();
|
||||
}
|
||||
|
||||
public async Task<Guid> CreateAsync(Guid userId, HealthRecordUpsertRequest request, CancellationToken ct)
|
||||
{
|
||||
var record = new HealthRecord
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = userId,
|
||||
MetricType = request.Type,
|
||||
Systolic = request.Systolic,
|
||||
Diastolic = request.Diastolic,
|
||||
Value = request.Value,
|
||||
Unit = request.Unit,
|
||||
Source = request.Source,
|
||||
RecordedAt = request.RecordedAt ?? DateTime.UtcNow,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsAbnormal = HealthRecordRules.CheckAbnormal(request),
|
||||
};
|
||||
|
||||
await _records.AddAsync(record, ct);
|
||||
await _records.SaveChangesAsync(ct);
|
||||
return record.Id;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Guid userId, Guid id, HealthRecordUpsertRequest request, CancellationToken ct)
|
||||
{
|
||||
var record = await _records.GetOwnedAsync(userId, id, ct);
|
||||
if (record == null) return false;
|
||||
|
||||
record.MetricType = request.Type;
|
||||
record.Systolic = request.Systolic;
|
||||
record.Diastolic = request.Diastolic;
|
||||
record.Value = request.Value;
|
||||
record.Unit = request.Unit;
|
||||
record.Source = request.Source;
|
||||
record.RecordedAt = request.RecordedAt ?? record.RecordedAt;
|
||||
record.IsAbnormal = HealthRecordRules.CheckAbnormal(request);
|
||||
|
||||
await _records.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid userId, Guid id, CancellationToken ct)
|
||||
{
|
||||
var record = await _records.GetOwnedAsync(userId, id, ct);
|
||||
if (record == null) return false;
|
||||
|
||||
_records.Delete(record);
|
||||
await _records.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object?>> GetLatestAsync(Guid userId, CancellationToken ct)
|
||||
{
|
||||
var types = new[]
|
||||
{
|
||||
HealthMetricType.BloodPressure,
|
||||
HealthMetricType.HeartRate,
|
||||
HealthMetricType.Glucose,
|
||||
HealthMetricType.SpO2,
|
||||
HealthMetricType.Weight
|
||||
};
|
||||
|
||||
var result = new Dictionary<string, object?>();
|
||||
foreach (var type in types)
|
||||
{
|
||||
var latest = await _records.GetLatestByTypeAsync(userId, type, ct);
|
||||
result[type.ToString()] = latest == null ? null : new
|
||||
{
|
||||
latest.Systolic,
|
||||
latest.Diastolic,
|
||||
latest.Value,
|
||||
latest.Unit,
|
||||
latest.RecordedAt
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<object>> GetTrendAsync(Guid userId, HealthMetricType type, int period, CancellationToken ct)
|
||||
{
|
||||
var days = period switch { 7 => 7, 30 => 30, 90 => 90, _ => 7 };
|
||||
var records = await _records.GetTrendAsync(userId, type, DateTime.UtcNow.AddDays(-days), ct);
|
||||
return records.Select(r => new { r.Id, r.Systolic, r.Diastolic, r.Value, r.IsAbnormal, r.RecordedAt }).Cast<object>().ToList();
|
||||
}
|
||||
|
||||
public static HealthRecordDto ToDto(HealthRecord record) => new(
|
||||
record.Id,
|
||||
record.MetricType.ToString(),
|
||||
record.Systolic,
|
||||
record.Diastolic,
|
||||
record.Value,
|
||||
record.Unit,
|
||||
record.Source.ToString(),
|
||||
record.IsAbnormal,
|
||||
record.RecordedAt);
|
||||
}
|
||||
Reference in New Issue
Block a user