feat: 引入 EF Core Migrations + 后台任务/校验修复 + 前端四态
后端 - 改用 EF Core Migrations(InitialCreate),Program.cs 用 MigrateAsync + AUTO_MIGRATE 开关 + 显式 MigrationsAssembly;移除 EnsureCreated、删除手写 DatabaseSchemaMigrator - 修复后台任务永久卡 Processing:三个队列对超时且达上限的任务原子标记 Failed - 修复报告重试失效:基础设施异常向上抛激活 RetryAsync,停机取消不计失败 - 修复 AI 用药天数 off-by-one(结束日为包含式) - AI 报告日志不再记录 VLM 健康正文 - 新增统一输入校验(ValidationException + 中间件映射 400):血压/心率/血糖/血氧/体重范围、药名/日期/服药时间去重、饮食热量与评分、运动时长上限 - 删除健康数据 AI 录入的影子直写路径,统一走 Service 校验 前端 - 新增 AppErrorState / AppFutureView,用药列表、服药打卡、运动计划、随访列表改为 加载/失败/空/数据 四态,失败可重试 瘦身 - 删除过时的 DataSeeder / DevDataSeeder 及调用 - 删除依赖实时服务的 ai_agent_tests 集成测试
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Health.Domain;
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
|
||||
@@ -5,6 +6,7 @@ namespace Health.Application.Diets;
|
||||
|
||||
public sealed class DietService(IDietRepository diets) : IDietService
|
||||
{
|
||||
private const int MaxCalories = 20000; // 单条记录/单项热量上限(kcal)
|
||||
private readonly IDietRepository _diets = diets;
|
||||
|
||||
public async Task<IReadOnlyList<DietRecordDto>> ListAsync(Guid userId, string? date, string? mealType, CancellationToken ct)
|
||||
@@ -18,6 +20,15 @@ public sealed class DietService(IDietRepository diets) : IDietService
|
||||
|
||||
public async Task<Guid> CreateAsync(Guid userId, DietRecordCreateRequest request, CancellationToken ct)
|
||||
{
|
||||
ValidateCalories(request.TotalCalories, "总热量");
|
||||
ValidateScore(request.HealthScore);
|
||||
foreach (var item in request.FoodItems)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Name))
|
||||
throw new ValidationException("食物名称不能为空");
|
||||
ValidateCalories(item.Calories, $"食物「{item.Name.Trim()}」热量");
|
||||
}
|
||||
|
||||
var record = new DietRecord
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -34,7 +45,7 @@ public sealed class DietService(IDietRepository diets) : IDietService
|
||||
record.FoodItems.Add(new DietFoodItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = item.Name,
|
||||
Name = item.Name.Trim(),
|
||||
Portion = item.Portion,
|
||||
Calories = item.Calories,
|
||||
SortOrder = item.SortOrder,
|
||||
@@ -61,12 +72,27 @@ public sealed class DietService(IDietRepository diets) : IDietService
|
||||
var record = await _diets.GetOwnedAsync(userId, recordId, ct);
|
||||
if (record == null) return false;
|
||||
|
||||
ValidateCalories(request.TotalCalories, "总热量");
|
||||
ValidateScore(request.HealthScore);
|
||||
|
||||
if (request.TotalCalories.HasValue) record.TotalCalories = request.TotalCalories.Value;
|
||||
if (request.HealthScore.HasValue) record.HealthScore = request.HealthScore.Value;
|
||||
await _diets.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ValidateCalories(int? calories, string field)
|
||||
{
|
||||
if (calories.HasValue && (calories.Value < 0 || calories.Value > MaxCalories))
|
||||
throw new ValidationException($"{field}应在 0~{MaxCalories} kcal 之间,当前值 {calories.Value} 不合理");
|
||||
}
|
||||
|
||||
private static void ValidateScore(int? score)
|
||||
{
|
||||
if (score.HasValue && (score.Value < 0 || score.Value > 100))
|
||||
throw new ValidationException($"健康评分应在 0~100 之间,当前值 {score.Value} 不合理");
|
||||
}
|
||||
|
||||
private static DietRecordDto ToDto(DietRecord record) => new(
|
||||
record.Id,
|
||||
record.MealType.ToString(),
|
||||
|
||||
Reference in New Issue
Block a user