Initial commit: 健康管家 AI 健康陪伴助手
- Backend: .NET 10 Minimal API + EF Core + PostgreSQL - Frontend: Flutter + Riverpod + GoRouter + Dio - AI: DeepSeek LLM + Qwen VLM (OpenAI-compatible) - Auth: SMS + JWT (access/refresh tokens) - Features: AI chat, health tracking, medication management, diet analysis, exercise plans, doctor consultations, report analysis
This commit is contained in:
132
backend/src/Health.WebApi/Endpoints/HealthEndpoints.cs
Normal file
132
backend/src/Health.WebApi/Endpoints/HealthEndpoints.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Health.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// 健康数据 API 端点
|
||||
/// </summary>
|
||||
public static class HealthEndpoints
|
||||
{
|
||||
public static void MapHealthEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/health-records").RequireAuthorization();
|
||||
|
||||
// 查询健康记录
|
||||
group.MapGet("/", async (
|
||||
string? type, int? days,
|
||||
HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var query = db.HealthRecords.Where(r => r.UserId == userId);
|
||||
|
||||
if (!string.IsNullOrEmpty(type) && Enum.TryParse<HealthMetricType>(type, ignoreCase: true, out var mt))
|
||||
query = query.Where(r => r.MetricType == mt);
|
||||
|
||||
if (days.HasValue)
|
||||
query = query.Where(r => r.RecordedAt >= DateTime.UtcNow.AddDays(-days.Value));
|
||||
|
||||
var records = await query.OrderByDescending(r => r.RecordedAt).Take(100)
|
||||
.Select(r => new
|
||||
{
|
||||
r.Id, Type = r.MetricType.ToString(), r.Systolic, r.Diastolic, r.Value, r.Unit,
|
||||
Source = r.Source.ToString(), r.IsAbnormal, r.RecordedAt
|
||||
}).ToListAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = records, message = (string?)null });
|
||||
});
|
||||
|
||||
// 新增健康记录
|
||||
group.MapPost("/", async (CreateHealthRecordRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var record = new HealthRecord
|
||||
{
|
||||
Id = Guid.NewGuid(), UserId = userId, MetricType = req.Type,
|
||||
Systolic = req.Systolic, Diastolic = req.Diastolic, Value = req.Value,
|
||||
Unit = req.Unit, Source = req.Source, RecordedAt = req.RecordedAt ?? DateTime.UtcNow,
|
||||
IsAbnormal = CheckAbnormal(req),
|
||||
};
|
||||
db.HealthRecords.Add(record);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { record.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 修改健康记录
|
||||
group.MapPut("/{id:guid}", async (Guid id, CreateHealthRecordRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var record = await db.HealthRecords.FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId, ct);
|
||||
if (record == null) return Results.Ok(new { code = 40004, data = (object?)null, message = "记录不存在" });
|
||||
|
||||
record.Systolic = req.Systolic;
|
||||
record.Diastolic = req.Diastolic;
|
||||
record.Value = req.Value;
|
||||
record.Unit = req.Unit;
|
||||
record.RecordedAt = req.RecordedAt ?? record.RecordedAt;
|
||||
record.IsAbnormal = CheckAbnormal(req);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 获取各指标最新值
|
||||
group.MapGet("/latest", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var types = new[] { HealthMetricType.BloodPressure, HealthMetricType.HeartRate, HealthMetricType.Glucose, HealthMetricType.SpO2, HealthMetricType.Weight };
|
||||
var result = new Dictionary<string, object?>();
|
||||
|
||||
foreach (var t in types)
|
||||
{
|
||||
var latest = await db.HealthRecords
|
||||
.Where(r => r.UserId == userId && r.MetricType == t)
|
||||
.OrderByDescending(r => r.RecordedAt)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
result[t.ToString()] = latest == null ? null : new
|
||||
{
|
||||
latest.Systolic, latest.Diastolic, latest.Value, latest.Unit, latest.RecordedAt
|
||||
};
|
||||
}
|
||||
|
||||
return Results.Ok(new { code = 0, data = result, message = (string?)null });
|
||||
});
|
||||
|
||||
// 趋势数据
|
||||
group.MapGet("/trend", async (string type, int period, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
if (!Enum.TryParse<HealthMetricType>(type, ignoreCase: true, out var mt))
|
||||
return Results.Ok(new { code = 40001, data = (object?)null, message = "不支持的指标类型" });
|
||||
|
||||
var days = period switch { 7 => 7, 30 => 30, 90 => 90, _ => 7 };
|
||||
var records = await db.HealthRecords
|
||||
.Where(r => r.UserId == userId && r.MetricType == mt && r.RecordedAt >= DateTime.UtcNow.AddDays(-days))
|
||||
.OrderBy(r => r.RecordedAt)
|
||||
.Select(r => new { r.Id, r.Systolic, r.Diastolic, r.Value, r.IsAbnormal, r.RecordedAt })
|
||||
.ToListAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = records, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
private static bool CheckAbnormal(CreateHealthRecordRequest req) => req.Type switch
|
||||
{
|
||||
HealthMetricType.BloodPressure => req.Systolic >= 140 || req.Diastolic >= 90 || req.Systolic <= 89 || req.Diastolic <= 59,
|
||||
HealthMetricType.HeartRate => req.Value > 100 || req.Value < 60,
|
||||
HealthMetricType.Glucose => req.Value >= 7.0m || req.Value <= 3.8m,
|
||||
HealthMetricType.SpO2 => req.Value <= 94,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static Guid GetUserId(HttpContext http) =>
|
||||
Guid.TryParse(http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty;
|
||||
}
|
||||
|
||||
public sealed record CreateHealthRecordRequest(
|
||||
HealthMetricType Type, int? Systolic, int? Diastolic, decimal? Value,
|
||||
string? Unit, HealthRecordSource Source, DateTime? RecordedAt);
|
||||
Reference in New Issue
Block a user