feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -1,105 +1,58 @@
|
||||
using Health.Application.HealthArchives;
|
||||
using Health.Application.Users;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// 用户与健康档案 API 端点
|
||||
/// </summary>
|
||||
public static class UserEndpoints
|
||||
{
|
||||
public static void MapUserEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/user").RequireAuthorization();
|
||||
|
||||
// 获取个人信息
|
||||
group.MapGet("/profile", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
group.MapGet("/profile", async (HttpContext http, IUserService users, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var user = await db.Users.Select(u => new
|
||||
{
|
||||
u.Id, u.Phone, u.Role, u.Name, u.Gender, BirthDate = u.BirthDate != null ? u.BirthDate.Value.ToString("yyyy-MM-dd") : null, u.AvatarUrl
|
||||
}).FirstOrDefaultAsync(u => u.Id == userId, ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = user, message = (string?)null });
|
||||
var profile = await users.GetProfileAsync(GetUserId(http), ct);
|
||||
return Results.Ok(new { code = 0, data = profile, message = (string?)null });
|
||||
});
|
||||
|
||||
// 修改资料
|
||||
group.MapPut("/profile", async (UpdateProfileRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
group.MapPut("/profile", async (UpdateProfileRequest req, HttpContext http, IUserService users, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var user = await db.Users.FindAsync([userId], ct);
|
||||
if (user == null) return Results.Ok(new { code = 40004, message = "用户不存在" });
|
||||
|
||||
user.Name = req.Name ?? user.Name;
|
||||
user.Gender = req.Gender ?? user.Gender;
|
||||
if (DateOnly.TryParse(req.BirthDate, out var bd)) user.BirthDate = bd;
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
var birthDate = DateOnly.TryParse(req.BirthDate, out var parsed) ? parsed : (DateOnly?)null;
|
||||
var updated = await users.UpdateProfileAsync(
|
||||
GetUserId(http),
|
||||
new UserProfileUpdateRequest(req.Name, req.Gender, birthDate),
|
||||
ct);
|
||||
if (!updated) return Results.Ok(new { code = 40004, message = "用户不存在" });
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 获取健康档案
|
||||
group.MapGet("/health-archive", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
group.MapGet("/health-archive", async (HttpContext http, IHealthArchiveService archives, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var archive = await db.HealthArchives.FirstOrDefaultAsync(a => a.UserId == userId, ct);
|
||||
var archive = await archives.GetAsync(GetUserId(http), ct);
|
||||
return Results.Ok(new { code = 0, data = archive, message = (string?)null });
|
||||
});
|
||||
|
||||
// 更新健康档案
|
||||
group.MapPut("/health-archive", async (UpdateArchiveRequest req, HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
group.MapPut("/health-archive", async (UpdateArchiveRequest req, HttpContext http, IHealthArchiveService archives, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
var archive = await db.HealthArchives.FirstOrDefaultAsync(a => a.UserId == userId, ct);
|
||||
if (archive == null)
|
||||
{
|
||||
archive = new HealthArchive { Id = Guid.NewGuid(), UserId = userId };
|
||||
db.HealthArchives.Add(archive);
|
||||
}
|
||||
|
||||
archive.Diagnosis = req.Diagnosis ?? archive.Diagnosis;
|
||||
archive.SurgeryType = req.SurgeryType ?? archive.SurgeryType;
|
||||
if (DateOnly.TryParse(req.SurgeryDate, out var sd)) archive.SurgeryDate = sd;
|
||||
if (req.Allergies != null) archive.Allergies = req.Allergies;
|
||||
if (req.DietRestrictions != null) archive.DietRestrictions = req.DietRestrictions;
|
||||
if (req.ChronicDiseases != null) archive.ChronicDiseases = req.ChronicDiseases;
|
||||
archive.FamilyHistory = req.FamilyHistory ?? archive.FamilyHistory;
|
||||
archive.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
var surgeryDate = DateOnly.TryParse(req.SurgeryDate, out var parsed) ? parsed : (DateOnly?)null;
|
||||
await archives.UpdateAsync(
|
||||
GetUserId(http),
|
||||
new HealthArchiveUpdateRequest(
|
||||
req.Diagnosis,
|
||||
req.SurgeryType,
|
||||
surgeryDate,
|
||||
req.Allergies,
|
||||
req.DietRestrictions,
|
||||
req.ChronicDiseases,
|
||||
req.FamilyHistory),
|
||||
ct);
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 注销账号
|
||||
group.MapDelete("/account", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
|
||||
group.MapDelete("/account", async (HttpContext http, IUserService users, CancellationToken ct) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
// 医生:清除Doctor关联
|
||||
var profile = await db.DoctorProfiles.FirstOrDefaultAsync(p => p.UserId == userId, ct);
|
||||
if (profile?.DoctorId != null)
|
||||
{
|
||||
await db.Users.Where(u => u.DoctorId == profile!.DoctorId).ExecuteUpdateAsync(u => u.SetProperty(x => x.DoctorId, (Guid?)null), ct);
|
||||
await db.Doctors.Where(d => d.Id == profile.DoctorId).ExecuteDeleteAsync(ct);
|
||||
}
|
||||
// 删除所有关联数据
|
||||
await db.HealthRecords.Where(r => r.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.MedicationLogs.Where(l => l.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.Medications.Where(m => m.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.DietFoodItems.Where(i => i.DietRecord!.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.DietRecords.Where(d => d.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.ExercisePlanItems.Where(i => i.Plan!.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.ExercisePlans.Where(p => p.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.Reports.Where(r => r.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.ConversationMessages.Where(m => m.Conversation!.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.Conversations.Where(c => c.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.ConsultationMessages.Where(m => m.Consultation!.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.Consultations.Where(c => c.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.FollowUps.Where(f => f.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.RefreshTokens.Where(t => t.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.DeviceTokens.Where(t => t.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.NotificationPreferences.Where(p => p.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.HealthArchives.Where(a => a.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.DoctorProfiles.Where(p => p.UserId == userId).ExecuteDeleteAsync(ct);
|
||||
await db.Users.Where(u => u.Id == userId).ExecuteDeleteAsync(ct);
|
||||
await users.DeleteAccountAsync(GetUserId(http), ct);
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user