feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
90
backend/src/Health.Infrastructure/Admin/AdminService.cs
Normal file
90
backend/src/Health.Infrastructure/Admin/AdminService.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Health.Application.Admin;
|
||||
|
||||
namespace Health.Infrastructure.Admin;
|
||||
|
||||
public sealed class AdminService(AppDbContext db) : IAdminService
|
||||
{
|
||||
private readonly AppDbContext _db = db;
|
||||
|
||||
public async Task<AdminResult> ListDoctorsAsync(CancellationToken ct)
|
||||
{
|
||||
var doctors = await _db.Doctors.AsNoTracking().OrderBy(x => x.CreatedAt)
|
||||
.Select(x => new { x.Id, x.Name, x.Title, x.Department, x.Phone, x.ProfessionalDirection, x.IsActive, x.AvatarUrl, x.Introduction, x.CreatedAt })
|
||||
.ToListAsync(ct);
|
||||
return Ok(doctors);
|
||||
}
|
||||
|
||||
public async Task<AdminResult> AddDoctorAsync(AddDoctorCommand command, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command.Phone)) return Error(40001, "手机号不能为空");
|
||||
if (string.IsNullOrWhiteSpace(command.Name)) return Error(40002, "姓名不能为空");
|
||||
var doctor = new Doctor
|
||||
{
|
||||
Id = Guid.NewGuid(), Name = command.Name, Title = command.Title, Department = command.Department,
|
||||
Phone = command.Phone, ProfessionalDirection = command.ProfessionalDirection, IsActive = true, CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
_db.Doctors.Add(doctor);
|
||||
if (!await _db.Users.AnyAsync(x => x.Phone == command.Phone, ct))
|
||||
{
|
||||
var user = new User { Id = Guid.NewGuid(), Phone = command.Phone, Role = "Doctor", Name = command.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 = command.Name,
|
||||
Title = command.Title, Department = command.Department, IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id, doctor.Name });
|
||||
}
|
||||
|
||||
public async Task<AdminResult> UpdateDoctorAsync(Guid id, UpdateDoctorCommand command, CancellationToken ct)
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
if (command.Name != null) doctor.Name = command.Name;
|
||||
if (command.Title != null) doctor.Title = command.Title;
|
||||
if (command.Department != null) doctor.Department = command.Department;
|
||||
if (command.Phone != null) doctor.Phone = command.Phone;
|
||||
if (command.ProfessionalDirection != null) doctor.ProfessionalDirection = command.ProfessionalDirection;
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id });
|
||||
}
|
||||
|
||||
public async Task<AdminResult> ToggleDoctorAsync(Guid id, CancellationToken ct)
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
doctor.IsActive = !doctor.IsActive;
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { doctor.Id, doctor.IsActive });
|
||||
}
|
||||
|
||||
public async Task<AdminResult> DeleteDoctorAsync(Guid id, CancellationToken ct)
|
||||
{
|
||||
var doctor = await _db.Doctors.FindAsync([id], ct);
|
||||
if (doctor == null) return Error(404, "医生不存在");
|
||||
await _db.Users.Where(x => x.DoctorId == id).ExecuteUpdateAsync(s => s.SetProperty(x => x.DoctorId, (Guid?)null), ct);
|
||||
_db.Doctors.Remove(doctor);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
|
||||
public async Task<AdminResult> ListPatientsAsync(string? search, int page, int pageSize, CancellationToken ct)
|
||||
{
|
||||
page = Math.Max(page, 1);
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
var query = _db.Users.AsNoTracking().Where(x => x.Role == "User");
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
query = query.Where(x => (x.Name != null && x.Name.Contains(search)) || x.Phone.Contains(search));
|
||||
var total = await query.CountAsync(ct);
|
||||
var patients = await query.OrderByDescending(x => x.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize)
|
||||
.Select(x => new { x.Id, x.Name, x.Phone, x.Gender, x.BirthDate, x.CreatedAt, DoctorName = x.Doctor != null ? x.Doctor.Name : null, DoctorDepartment = x.Doctor != null ? x.Doctor.Department : null })
|
||||
.ToListAsync(ct);
|
||||
return Ok(new { patients, total, page, pageSize });
|
||||
}
|
||||
|
||||
private static AdminResult Ok(object data) => new(0, data);
|
||||
private static AdminResult Error(int code, string message) => new(code, null, message);
|
||||
}
|
||||
Reference in New Issue
Block a user