chore: 全面规范化代码,遵循 CLAUDE.md 编码规范
- C# 文件命名改为 snake_case(28 个文件重命名) - C# 类转换为主构造函数(8 个类) - 空 catch 添加异常类型(2 处) - 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi) - Flutter 移除 go_router,改用 Riverpod 路由栈 - Flutter 移除 flutter_secure_storage,改用 sqflite 持久化 - 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘) - 后端端口改为 0.0.0.0:5000,支持局域网访问
This commit is contained in:
261
backend/src/Health.WebApi/Endpoints/remaining_endpoints.cs
Normal file
261
backend/src/Health.WebApi/Endpoints/remaining_endpoints.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// 饮食、用药、报告、问诊、运动、文件端点
|
||||
/// </summary>
|
||||
public static class RemainingEndpoints
|
||||
{
|
||||
public static void MapDietEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/diet-records").RequireAuthorization();
|
||||
|
||||
group.MapGet("/", async (string? date, string? mealType, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var query = db.DietRecords.Include(d => d.FoodItems).Where(d => d.UserId == userId);
|
||||
if (DateOnly.TryParse(date, out var d)) query = query.Where(r => r.RecordedAt == d);
|
||||
if (Enum.TryParse<MealType>(mealType, ignoreCase: true, out var mt)) query = query.Where(r => r.MealType == mt);
|
||||
var records = await query.OrderByDescending(r => r.RecordedAt).ToListAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = records, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/", async (CreateDietRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var record = new DietRecord
|
||||
{
|
||||
Id = Guid.NewGuid(), UserId = userId, MealType = req.MealType,
|
||||
TotalCalories = req.TotalCalories, HealthScore = req.HealthScore, RecordedAt = req.RecordedAt ?? DateOnly.FromDateTime(DateTime.Now),
|
||||
};
|
||||
if (req.FoodItems != null)
|
||||
foreach (var fi in req.FoodItems)
|
||||
record.FoodItems.Add(new DietFoodItem { Id = Guid.NewGuid(), Name = fi.Name, Portion = fi.Portion, Calories = fi.Calories, ProteinGrams = fi.ProteinGrams, CarbsGrams = fi.CarbsGrams, FatGrams = fi.FatGrams, Warning = fi.Warning, SortOrder = fi.SortOrder });
|
||||
db.DietRecords.Add(record);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { record.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var record = await db.DietRecords.FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId, ct);
|
||||
if (record != null) { db.DietRecords.Remove(record); await db.SaveChangesAsync(ct); }
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
public static void MapMedicationEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/medications").RequireAuthorization();
|
||||
|
||||
group.MapGet("/", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var meds = await db.Medications.Where(m => m.UserId == userId).OrderByDescending(m => m.CreatedAt).ToListAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = meds, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/", async (CreateMedicationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var med = new Medication
|
||||
{
|
||||
Id = Guid.NewGuid(), UserId = userId, Name = req.Name, Dosage = req.Dosage,
|
||||
Frequency = req.Frequency, TimeOfDay = req.TimeOfDay ?? [],
|
||||
StartDate = req.StartDate, EndDate = req.EndDate, IsActive = true, Source = req.Source,
|
||||
};
|
||||
db.Medications.Add(med);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { med.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPut("/{id:guid}", async (Guid id, CreateMedicationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var med = await db.Medications.FirstOrDefaultAsync(m => m.Id == id && m.UserId == userId, ct);
|
||||
if (med == null) return Results.Ok(new { code = 40004, message = "不存在" });
|
||||
med.Name = req.Name; med.Dosage = req.Dosage; med.Frequency = req.Frequency;
|
||||
med.TimeOfDay = req.TimeOfDay ?? med.TimeOfDay; med.StartDate = req.StartDate; med.EndDate = req.EndDate;
|
||||
med.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var med = await db.Medications.FirstOrDefaultAsync(m => m.Id == id && m.UserId == userId, ct);
|
||||
if (med != null) { db.Medications.Remove(med); await db.SaveChangesAsync(ct); }
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/{id:guid}/confirm", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var log = new MedicationLog
|
||||
{
|
||||
Id = Guid.NewGuid(), MedicationId = id, UserId = userId,
|
||||
Status = MedicationLogStatus.Taken, ScheduledTime = TimeOnly.FromDateTime(DateTime.Now), ConfirmedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.MedicationLogs.Add(log);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
public static void MapReportEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/reports").RequireAuthorization();
|
||||
|
||||
group.MapGet("/", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var reports = await db.Reports.Where(r => r.UserId == userId).OrderByDescending(r => r.CreatedAt).ToListAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = reports, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapGet("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var report = await db.Reports.FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId, ct);
|
||||
return report == null ? Results.Ok(new { code = 40004, message = "不存在" }) : Results.Ok(new { code = 0, data = report, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
public static void MapConsultationEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api").RequireAuthorization();
|
||||
|
||||
group.MapGet("/doctors", async (AppDbContext db) =>
|
||||
{
|
||||
var doctors = await db.Doctors.Where(d => d.IsActive).Select(d => new { d.Id, d.Name, d.Title, d.Department, d.Introduction }).ToListAsync();
|
||||
return Results.Ok(new { code = 0, data = doctors, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapGet("/consultations", async (HttpContext http, AppDbContext db) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var consultations = await db.Consultations.Where(c => c.UserId == userId).OrderByDescending(c => c.CreatedAt).ToListAsync();
|
||||
return Results.Ok(new { code = 0, data = consultations, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/consultations", async (CreateConsultationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var consultation = new Consultation
|
||||
{
|
||||
Id = Guid.NewGuid(), UserId = userId, DoctorId = req.DoctorId,
|
||||
Status = ConsultationStatus.AiTalking,
|
||||
Month = DateTime.UtcNow.Year * 100 + DateTime.UtcNow.Month,
|
||||
};
|
||||
db.Consultations.Add(consultation);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { consultation.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapGet("/consultations/{id:guid}/messages", async (Guid id, string? after, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var query = db.ConsultationMessages.Where(m => m.ConsultationId == id && m.Consultation.UserId == userId);
|
||||
if (Guid.TryParse(after, out var afterId))
|
||||
query = query.Where(m => m.Id.CompareTo(afterId) > 0);
|
||||
var messages = await query.OrderBy(m => m.CreatedAt).Take(50).Select(m => new { m.Id, SenderType = m.SenderType.ToString(), m.SenderName, m.Content, m.CreatedAt }).ToListAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = messages, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/consultations/{id:guid}/messages", async (Guid id, SendMessageRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var msg = new ConsultationMessage { Id = Guid.NewGuid(), ConsultationId = id, SenderType = ConsultationSenderType.User, Content = req.Content, SenderName = null, CreatedAt = DateTime.UtcNow };
|
||||
db.ConsultationMessages.Add(msg);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { msg.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapGet("/user/consultation-quota", async (HttpContext http, AppDbContext db) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var now = DateTime.UtcNow;
|
||||
// 用年月组合值避免跨年问题:202601=2026年1月
|
||||
var currentPeriod = now.Year * 100 + now.Month;
|
||||
var used = await db.Consultations.CountAsync(c => c.UserId == userId && c.Month == currentPeriod);
|
||||
return Results.Ok(new { code = 0, data = new { total = 3, used, remaining = 3 - used }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
public static void MapExerciseEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/exercise-plans").RequireAuthorization();
|
||||
|
||||
group.MapGet("/current", async (HttpContext http, AppDbContext db) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var today = DateOnly.FromDateTime(DateTime.Now);
|
||||
var monday = today.AddDays(-(int)today.DayOfWeek + 1);
|
||||
var plan = await db.ExercisePlans.Include(p => p.Items).FirstOrDefaultAsync(p => p.UserId == userId && p.WeekStartDate == monday);
|
||||
return Results.Ok(new { code = 0, data = plan, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/", async (CreateExercisePlanRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var plan = new ExercisePlan { Id = Guid.NewGuid(), UserId = userId, WeekStartDate = req.WeekStartDate };
|
||||
if (req.Items != null)
|
||||
foreach (var item in req.Items)
|
||||
plan.Items.Add(new ExercisePlanItem { Id = Guid.NewGuid(), DayOfWeek = item.DayOfWeek, ExerciseType = item.ExerciseType, DurationMinutes = item.DurationMinutes, IsRestDay = item.IsRestDay });
|
||||
db.ExercisePlans.Add(plan);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { plan.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
group.MapPost("/items/{itemId:guid}/checkin", async (Guid itemId, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var item = await db.ExercisePlanItems.FindAsync([itemId], ct);
|
||||
if (item == null) return Results.Ok(new { code = 40004, message = "不存在" });
|
||||
item.IsCompleted = true; item.CompletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
public static void MapFileEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/files").RequireAuthorization();
|
||||
|
||||
group.MapPost("/upload", async (HttpRequest request) =>
|
||||
{
|
||||
var form = await request.ReadFormAsync();
|
||||
var files = form.Files;
|
||||
var results = new List<object>();
|
||||
var uploadsDir = Path.Combine(Directory.GetCurrentDirectory(), "uploads");
|
||||
Directory.CreateDirectory(uploadsDir);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var fileId = Guid.NewGuid().ToString();
|
||||
var ext = Path.GetExtension(file.FileName);
|
||||
var filePath = Path.Combine(uploadsDir, $"{fileId}{ext}");
|
||||
using var stream = new FileStream(filePath, FileMode.Create);
|
||||
await file.CopyToAsync(stream);
|
||||
results.Add(new { id = fileId, name = file.FileName, size = file.Length });
|
||||
}
|
||||
|
||||
return Results.Ok(new { code = 0, data = results, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
private static Guid GetUserId(HttpContext http) =>
|
||||
Guid.TryParse(http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty;
|
||||
}
|
||||
|
||||
// ---- 请求 DTO ----
|
||||
public sealed record CreateDietRequest(MealType MealType, int? TotalCalories, int? HealthScore, DateOnly? RecordedAt, List<FoodItemDto>? FoodItems);
|
||||
public sealed record FoodItemDto(string Name, string? Portion, int? Calories, decimal? ProteinGrams, decimal? CarbsGrams, decimal? FatGrams, string? Warning, int SortOrder);
|
||||
|
||||
public sealed record CreateMedicationRequest(string Name, string? Dosage, MedicationFrequency Frequency, List<TimeOnly>? TimeOfDay, DateOnly? StartDate, DateOnly? EndDate, MedicationSource Source);
|
||||
|
||||
public sealed record CreateConsultationRequest(Guid DoctorId);
|
||||
public sealed record SendMessageRequest(string Content);
|
||||
|
||||
public sealed record CreateExercisePlanRequest(DateOnly WeekStartDate, List<ExerciseItemDto>? Items);
|
||||
public sealed record ExerciseItemDto(int DayOfWeek, string ExerciseType, int DurationMinutes, bool IsRestDay);
|
||||
Reference in New Issue
Block a user