fix: 管理员系统 + 注册流程重构 + UI改版 + 全链路修复
- 新增管理员角色(手机号12345678910),管理员可增删医生、查看患者 - 注册页重构:去掉角色选择+审核码,改为选医生+填姓名 - 医生端点按DoctorId过滤患者,Patient↔Doctor关系建立 - Doctor/DoctorProfile/User实体新增关联字段 - JSON循环引用修复(IgnoreCycles) - /api/doctors改为公开接口 - 登录闪屏修复+原生Android启动页 - 输入框全局白底+灰框 - 蓝牙重连同步修复 - Web端(doctor_web+health_app/web)全部删除 - 全局UI改版:白底企业风,新配色和组件系统 - 新品牌图标和启动图
This commit is contained in:
@@ -44,6 +44,8 @@ public sealed class Doctor
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Title { get; set; } // 主任医师/副主任医师
|
||||
public string? Department { get; set; } // 心血管内科/营养科
|
||||
public string? Phone { get; set; } // 医生手机号
|
||||
public string? ProfessionalDirection { get; set; } // 专业方向
|
||||
public string? AvatarUrl { get; set; }
|
||||
public string? Introduction { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
@@ -7,7 +7,9 @@ public sealed class DoctorProfile
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid? DoctorId { get; set; } // FK → Doctor 实体
|
||||
public string? Name { get; set; }
|
||||
public Doctor? Doctor { get; set; } // 导航属性
|
||||
public string? Title { get; set; }
|
||||
public string? Department { get; set; }
|
||||
public string? Hospital { get; set; }
|
||||
|
||||
@@ -7,8 +7,10 @@ public sealed class User
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = "User"; // "User" | "Doctor"
|
||||
public string Role { get; set; } = "User"; // "User" | "Doctor" | "Admin"
|
||||
public Guid? DoctorId { get; set; } // 患者选择的医生
|
||||
public string? Name { get; set; }
|
||||
public Doctor? Doctor { get; set; } // 导航属性
|
||||
public string? Gender { get; set; }
|
||||
public DateOnly? BirthDate { get; set; }
|
||||
public string? AvatarUrl { get; set; }
|
||||
|
||||
@@ -43,6 +43,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbCon
|
||||
{
|
||||
e.HasIndex(u => u.Phone).IsUnique();
|
||||
e.Property(u => u.Role).HasMaxLength(32).HasDefaultValue("User");
|
||||
e.HasOne(u => u.Doctor).WithMany().HasForeignKey(u => u.DoctorId).IsRequired(false).OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
// ---- Doctor ----
|
||||
builder.Entity<Doctor>(e =>
|
||||
{
|
||||
e.Property(d => d.Phone).HasMaxLength(20);
|
||||
e.Property(d => d.ProfessionalDirection).HasMaxLength(256);
|
||||
});
|
||||
|
||||
// ---- DoctorProfile ----
|
||||
@@ -50,6 +58,7 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbCon
|
||||
{
|
||||
e.HasIndex(d => d.UserId).IsUnique();
|
||||
e.HasOne(d => d.User).WithOne(u => u.DoctorProfile).HasForeignKey<DoctorProfile>(d => d.UserId);
|
||||
e.HasOne(d => d.Doctor).WithMany().HasForeignKey(d => d.DoctorId).IsRequired(false).OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
// ---- HealthRecord ----
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Health.Domain.Entities;
|
||||
using Health.Domain.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Health.Infrastructure.Data;
|
||||
@@ -20,11 +22,41 @@ public static class DevDataSeeder
|
||||
// 检查是否已有测试用户(避免重复填充)
|
||||
if (db.Users.Any(u => u.Phone == "13800000001")) return;
|
||||
|
||||
// ---- 种子医生数据 ----
|
||||
if (!db.Doctors.Any())
|
||||
{
|
||||
var doctor1 = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(), Name = "张明", Title = "主任医师",
|
||||
Department = "心脏康复科", Phone = "13800000002",
|
||||
ProfessionalDirection = "冠心病康复、术后管理",
|
||||
IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
var doctor2 = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(), Name = "李芳", Title = "副主任医师",
|
||||
Department = "营养科", Phone = "13800000003",
|
||||
ProfessionalDirection = "糖尿病管理、饮食指导",
|
||||
IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
var doctor3 = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(), Name = "王建国", Title = "主任医师",
|
||||
Department = "心血管内科", Phone = "13800000004",
|
||||
ProfessionalDirection = "高血压管理、PCI术后随访",
|
||||
IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Doctors.AddRange(doctor1, doctor2, doctor3);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ---- 创建测试患者 ----
|
||||
var doctorWang = db.Doctors.FirstOrDefault(d => d.Name == "王建国");
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(), Phone = "13800000001", Name = "张三",
|
||||
Gender = "男", BirthDate = new DateOnly(1970, 3, 15),
|
||||
DoctorId = doctorWang?.Id,
|
||||
CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
|
||||
201
backend/src/Health.WebApi/Endpoints/admin_endpoints.cs
Normal file
201
backend/src/Health.WebApi/Endpoints/admin_endpoints.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System.Security.Claims;
|
||||
using Health.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// 管理员 API(需JWT鉴权 + Role=Admin)
|
||||
/// </summary>
|
||||
public static class AdminEndpoints
|
||||
{
|
||||
private static readonly Guid AdminId = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static Guid GetUserId(HttpContext http) =>
|
||||
Guid.TryParse(http.User.FindFirst(ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty;
|
||||
|
||||
private static string GetUserRole(HttpContext http) =>
|
||||
http.User.FindFirst("Role")?.Value ?? http.User.FindFirst(ClaimTypes.Role)?.Value ?? "User";
|
||||
|
||||
public static void MapAdminEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/admin").RequireAuthorization();
|
||||
|
||||
// 管理员身份校验
|
||||
group.AddEndpointFilter(async (context, next) =>
|
||||
{
|
||||
var role = GetUserRole(context.HttpContext);
|
||||
if (role != "Admin")
|
||||
return Results.Json(new { code = 403, data = (object?)null, message = "仅管理员可访问" }, statusCode: 403);
|
||||
return await next(context);
|
||||
});
|
||||
|
||||
// ===== 医生列表 =====
|
||||
group.MapGet("/doctors", async (AppDbContext db) =>
|
||||
{
|
||||
var doctors = await db.Doctors
|
||||
.OrderBy(d => d.CreatedAt)
|
||||
.Select(d => new
|
||||
{
|
||||
d.Id, d.Name, d.Title, d.Department,
|
||||
d.Phone, d.ProfessionalDirection,
|
||||
d.IsActive, d.AvatarUrl, d.Introduction,
|
||||
d.CreatedAt
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return Results.Ok(new { code = 0, data = doctors, message = (string?)null });
|
||||
});
|
||||
|
||||
// ===== 新增医生 =====
|
||||
group.MapPost("/doctors", async (
|
||||
AddDoctorRequest request,
|
||||
AppDbContext db,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Phone))
|
||||
return Results.Ok(new { code = 40001, data = (object?)null, message = "手机号不能为空" });
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "姓名不能为空" });
|
||||
|
||||
var doctor = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = request.Name,
|
||||
Title = request.Title,
|
||||
Department = request.Department,
|
||||
Phone = request.Phone,
|
||||
ProfessionalDirection = request.ProfessionalDirection,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Doctors.Add(doctor);
|
||||
|
||||
// 同步创建 User + DoctorProfile,医生可用手机号登录
|
||||
var existingUser = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
if (existingUser == null)
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
Role = "Doctor",
|
||||
Name = request.Name,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
db.DoctorProfiles.Add(new DoctorProfile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
DoctorId = doctor.Id,
|
||||
Name = request.Name,
|
||||
Title = request.Title,
|
||||
Department = request.Department,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { doctor.Id, doctor.Name }, message = (string?)null });
|
||||
});
|
||||
|
||||
// ===== 编辑医生 =====
|
||||
group.MapPut("/doctors/{id:guid}", async (
|
||||
Guid id, UpdateDoctorRequest request, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var doctor = await db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null)
|
||||
return Results.Ok(new { code = 404, data = (object?)null, message = "医生不存在" });
|
||||
|
||||
if (request.Name != null) doctor.Name = request.Name;
|
||||
if (request.Title != null) doctor.Title = request.Title;
|
||||
if (request.Department != null) doctor.Department = request.Department;
|
||||
if (request.Phone != null) doctor.Phone = request.Phone;
|
||||
if (request.ProfessionalDirection != null) doctor.ProfessionalDirection = request.ProfessionalDirection;
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new { code = 0, data = new { doctor.Id }, message = (string?)null });
|
||||
});
|
||||
|
||||
// ===== 启用/停用医生 =====
|
||||
group.MapPut("/doctors/{id:guid}/disable", async (
|
||||
Guid id, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var doctor = await db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null)
|
||||
return Results.Ok(new { code = 404, data = (object?)null, message = "医生不存在" });
|
||||
|
||||
doctor.IsActive = !doctor.IsActive;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { doctor.Id, doctor.IsActive }, message = (string?)null });
|
||||
});
|
||||
|
||||
// ===== 删除医生 =====
|
||||
group.MapDelete("/doctors/{id:guid}", async (
|
||||
Guid id, AppDbContext db, CancellationToken ct) =>
|
||||
{
|
||||
var doctor = await db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null)
|
||||
return Results.Ok(new { code = 404, data = (object?)null, message = "医生不存在" });
|
||||
|
||||
// 清除患者的 DoctorId 关联
|
||||
await db.Users.Where(u => u.DoctorId == id)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(u => u.DoctorId, (Guid?)null), ct);
|
||||
|
||||
db.Doctors.Remove(doctor);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
|
||||
// ===== 患者列表 =====
|
||||
group.MapGet("/patients", async (
|
||||
AppDbContext db,
|
||||
string? search,
|
||||
int? page,
|
||||
int? pageSize) =>
|
||||
{
|
||||
var p = page ?? 1;
|
||||
var ps = pageSize ?? 20;
|
||||
var query = db.Users
|
||||
.Where(u => u.Role == "User")
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
query = query.Where(u => u.Name!.Contains(search) || u.Phone.Contains(search));
|
||||
}
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var patients = await query
|
||||
.OrderByDescending(u => u.CreatedAt)
|
||||
.Skip((p - 1) * ps)
|
||||
.Take(ps)
|
||||
.Select(u => new
|
||||
{
|
||||
u.Id, u.Name, u.Phone, u.Gender,
|
||||
u.BirthDate, u.CreatedAt,
|
||||
DoctorName = u.Doctor != null ? u.Doctor.Name : null,
|
||||
DoctorDepartment = u.Doctor != null ? u.Doctor.Department : null,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new { patients, total, page = p, pageSize = ps },
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ---- DTO ----
|
||||
public sealed record AddDoctorRequest(string Phone, string Name, string? Title, string? Department, string? ProfessionalDirection);
|
||||
public sealed record UpdateDoctorRequest(string? Phone, string? Name, string? Title, string? Department, string? ProfessionalDirection);
|
||||
@@ -7,10 +7,8 @@ namespace Health.WebApi.Endpoints;
|
||||
/// </summary>
|
||||
public static class AuthEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// 医生注册审核码(后期可移到配置表)
|
||||
/// </summary>
|
||||
private const string DoctorInviteCode = "6666";
|
||||
private const string AdminPhone = "12345678910";
|
||||
private const string AdminSmsCode = "000000";
|
||||
|
||||
public static void MapAuthEndpoints(this WebApplication app)
|
||||
{
|
||||
@@ -21,8 +19,7 @@ public static class AuthEndpoints
|
||||
SmsService sms,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
// 生成验证码
|
||||
var code = sms.GenerateCode();
|
||||
var code = request.Phone == AdminPhone ? AdminSmsCode : sms.GenerateCode();
|
||||
var vc = new VerificationCode
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -33,13 +30,13 @@ public static class AuthEndpoints
|
||||
db.VerificationCodes.Add(vc);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// 开发阶段:直接返回验证码(生产环境需去掉 devCode)
|
||||
await sms.SendCodeAsync(request.Phone, code);
|
||||
if (request.Phone != AdminPhone)
|
||||
await sms.SendCodeAsync(request.Phone, code);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true, devCode = code }, message = (string?)null });
|
||||
});
|
||||
|
||||
// ── 注册(新用户,需选身份)──
|
||||
// ── 注册(仅限用户注册,需选择医生)──
|
||||
app.MapPost("/api/auth/register", async (
|
||||
RegisterRequest request,
|
||||
AppDbContext db,
|
||||
@@ -65,42 +62,33 @@ public static class AuthEndpoints
|
||||
if (existing != null)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "该手机号已注册,请直接登录" });
|
||||
|
||||
// 校验角色
|
||||
var role = request.Role;
|
||||
if (role != "User" && role != "Doctor")
|
||||
return Results.Ok(new { code = 40003, data = (object?)null, message = "角色参数无效" });
|
||||
// 校验姓名
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
return Results.Ok(new { code = 40003, data = (object?)null, message = "请输入姓名" });
|
||||
|
||||
// 医生注册需校验审核码
|
||||
if (role == "Doctor" && request.InviteCode != DoctorInviteCode)
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "审核码错误" });
|
||||
// 校验医生
|
||||
var doctor = await db.Doctors.FirstOrDefaultAsync(d => d.Id == request.DoctorId && d.IsActive, ct);
|
||||
if (doctor == null)
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "所选医生不存在或已停诊" });
|
||||
|
||||
// 创建用户
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
Role = role,
|
||||
Role = "User",
|
||||
Name = request.Name,
|
||||
DoctorId = request.DoctorId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
|
||||
// 用户端:创建通知偏好+健康档案
|
||||
if (role == "User")
|
||||
{
|
||||
db.NotificationPreferences.Add(new NotificationPreference { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
db.HealthArchives.Add(new HealthArchive { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
}
|
||||
|
||||
// 医生端:创建空医生档案
|
||||
if (role == "Doctor")
|
||||
{
|
||||
db.DoctorProfiles.Add(new DoctorProfile { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
}
|
||||
db.NotificationPreferences.Add(new NotificationPreference { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
db.HealthArchives.Add(new HealthArchive { Id = Guid.NewGuid(), UserId = user.Id });
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, role);
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, "User");
|
||||
var refreshToken = jwt.GenerateRefreshToken();
|
||||
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
@@ -119,15 +107,13 @@ public static class AuthEndpoints
|
||||
{
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user = new {
|
||||
user.Id, user.Phone, user.Role, isNew = true
|
||||
}
|
||||
user = new { user.Id, user.Phone, user.Role, isNew = true }
|
||||
},
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
|
||||
// ── 登录(已有账号)──
|
||||
// ── 登录(已有账号 / 管理员)──
|
||||
app.MapPost("/api/auth/login", async (
|
||||
LoginRequest request,
|
||||
AppDbContext db,
|
||||
@@ -147,9 +133,37 @@ public static class AuthEndpoints
|
||||
|
||||
validCode.IsUsed = true;
|
||||
|
||||
// 管理员登录
|
||||
if (request.Phone == AdminPhone)
|
||||
{
|
||||
var adminAccessToken = jwt.GenerateAccessToken(
|
||||
Guid.Parse("00000000-0000-0000-0000-000000000001"), AdminPhone, "Admin");
|
||||
var adminRefreshToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||
Token = adminRefreshToken,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new
|
||||
{
|
||||
accessToken = adminAccessToken,
|
||||
refreshToken = adminRefreshToken,
|
||||
user = new { id = "00000000-0000-0000-0000-000000000001", phone = AdminPhone, role = "Admin", name = "管理员", isNew = false }
|
||||
},
|
||||
message = (string?)null
|
||||
});
|
||||
}
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
if (user == null)
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "该手机号未注册,请先注册" });
|
||||
return Results.Ok(new { code = 40004, data = (object?)null, message = "该手机号未注册,请先登录" });
|
||||
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
@@ -197,14 +211,35 @@ public static class AuthEndpoints
|
||||
if (oldToken == null || oldToken.ExpiresAt < DateTime.UtcNow)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "登录已过期,请重新登录" });
|
||||
|
||||
// 吊销旧 token
|
||||
oldToken.IsRevoked = true;
|
||||
|
||||
var user = await db.Users.FindAsync([oldToken.UserId], ct);
|
||||
|
||||
// 管理员刷新:无 User 记录,直接用固定信息
|
||||
if (user == null && oldToken.UserId == Guid.Parse("00000000-0000-0000-0000-000000000001"))
|
||||
{
|
||||
var adminAccessToken = jwt.GenerateAccessToken(
|
||||
Guid.Parse("00000000-0000-0000-0000-000000000001"), AdminPhone, "Admin");
|
||||
var adminNewRefresh = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||
Token = adminNewRefresh,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new { accessToken = adminAccessToken, refreshToken = adminNewRefresh, user = new { role = "Admin" } },
|
||||
message = (string?)null
|
||||
});
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "用户不存在" });
|
||||
|
||||
// 生成新 token(续期)
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone, user.Role);
|
||||
var newRefreshToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
@@ -242,6 +277,6 @@ public static class AuthEndpoints
|
||||
|
||||
// ---- 请求 DTO ----
|
||||
public sealed record SendSmsRequest(string Phone);
|
||||
public sealed record RegisterRequest(string Phone, string SmsCode, string? Role, string? InviteCode);
|
||||
public sealed record RegisterRequest(string Phone, string SmsCode, string Name, Guid DoctorId);
|
||||
public sealed record LoginRequest(string Phone, string SmsCode);
|
||||
public sealed record RefreshRequest(string RefreshToken);
|
||||
|
||||
@@ -6,14 +6,15 @@ public static class ConsultationEndpoints
|
||||
{
|
||||
public static void MapConsultationEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api").RequireAuthorization();
|
||||
|
||||
group.MapGet("/doctors", async (AppDbContext db) =>
|
||||
// 医生列表(注册时无需登录)
|
||||
app.MapGet("/api/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 });
|
||||
});
|
||||
|
||||
var group = app.MapGroup("/api").RequireAuthorization();
|
||||
|
||||
group.MapGet("/consultations", async (HttpContext http, AppDbContext db) =>
|
||||
{
|
||||
var userId = GetUserId(http);
|
||||
|
||||
@@ -23,6 +23,12 @@ public static class DoctorEndpoints
|
||||
return await db.DoctorProfiles.FirstOrDefaultAsync(d => d.UserId == userId);
|
||||
}
|
||||
|
||||
private static async Task<Guid?> GetDoctorEntityId(HttpContext http, AppDbContext db)
|
||||
{
|
||||
var profile = await GetDoctorProfile(http, db);
|
||||
return profile?.DoctorId;
|
||||
}
|
||||
|
||||
public static void MapDoctorEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/doctor").RequireAuthorization();
|
||||
@@ -93,9 +99,12 @@ public static class DoctorEndpoints
|
||||
});
|
||||
|
||||
// ===== 患者列表 =====
|
||||
group.MapGet("/patients", async (AppDbContext db, string? search, int page = 1, int pageSize = 20) =>
|
||||
group.MapGet("/patients", async (HttpContext http, AppDbContext db, string? search, int page = 1, int pageSize = 20) =>
|
||||
{
|
||||
var query = db.Users.Where(u => u.Role == "User").AsQueryable();
|
||||
var doctorId = await GetDoctorEntityId(http, db);
|
||||
if (doctorId == null)
|
||||
return Results.Ok(new { code = 500, data = (object?)null, message = "医生档案未关联" });
|
||||
var query = db.Users.Where(u => u.Role == "User" && u.DoctorId == doctorId).AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
query = query.Where(u =>
|
||||
@@ -359,9 +368,13 @@ public static class DoctorEndpoints
|
||||
});
|
||||
|
||||
// ===== 患者列表(简版)=====
|
||||
group.MapGet("/patients-simple", async (AppDbContext db) =>
|
||||
group.MapGet("/patients-simple", async (HttpContext http, AppDbContext db) =>
|
||||
{
|
||||
var patients = await db.Users.Where(u => u.Role == "User").OrderBy(u => u.Name).Select(u => new { u.Id, u.Name, u.Phone }).ToListAsync();
|
||||
var doctorId = await GetDoctorEntityId(http, db);
|
||||
var query = db.Users.Where(u => u.Role == "User");
|
||||
if (doctorId != null)
|
||||
query = query.Where(u => u.DoctorId == doctorId);
|
||||
var patients = await query.OrderBy(u => u.Name).Select(u => new { u.Id, u.Name, u.Phone }).ToListAsync();
|
||||
return Results.Ok(new { code = 0, data = patients, message = (string?)null });
|
||||
});
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// ---- JSON 配置(枚举字符串、camelCase、UTC时间统一带Z)----
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
{
|
||||
options.SerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
|
||||
options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
||||
options.SerializerOptions.Converters.Add(new UtcDateTimeConverter());
|
||||
options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
|
||||
@@ -134,6 +135,7 @@ app.MapAiChatEndpoints();
|
||||
app.MapFileEndpoints();
|
||||
app.MapCalendarEndpoints();
|
||||
app.MapDoctorEndpoints();
|
||||
app.MapAdminEndpoints();
|
||||
app.MapFollowUpEndpoints();
|
||||
|
||||
// SignalR Hub
|
||||
|
||||
Reference in New Issue
Block a user