docs: split tech docs into backend/patient/doctor, add prod roadmap, improve upload UI, fix report list for doctors, auto-start frontends in bat

This commit is contained in:
MingNian
2026-05-21 11:21:43 +08:00
parent 5d89dcceeb
commit 51c7c89ec5
5 changed files with 1570 additions and 1517 deletions

View File

@@ -0,0 +1,487 @@
# HealthManager 后端 — 技术文档
> 适用对象:小白开发者 / 新加入项目的同学
> 涵盖项目总览、整洁架构、Domain/Application/Infrastructure/WebApi 四层、数据库、FAQ
---
## 目录
1. [项目总览](#1-项目总览)
2. [架构设计:什么是整洁架构](#2-架构设计什么是整洁架构)
3. [Domain 领域层](#3-domain-领域层)
4. [Application 应用层](#4-application-应用层)
5. [Infrastructure 基础设施层](#5-infrastructure-基础设施层)
6. [WebApi 接口层](#6-webapi-接口层)
7. [数据库表结构](#7-数据库表结构)
8. [常见问题 FAQ](#8-常见问题-faq)
---
## 1. 项目总览
### 1.1 这是什么项目?
**HealthManager** 是一个**面向心血管术后患者的健康管理平台**。
| 部分 | 技术 | 谁用 | 干什么 |
|------|------|------|--------|
| **后端 API** | .NET 10 + PostgreSQL | 为两个前端提供数据 | 用户认证、健康数据存储、用药管理、在线问诊、报告解读、随访管理 |
| **病人 H5 前端** | React 19 + TypeScript + Vite | 术后患者 | 在手机上记录血压/心率/用药,咨询医生,查看报告 |
| **医生 PC 前端** | React 19 + TypeScript + Vite | 心血管医生 | 在电脑上管理患者、回复咨询、解读报告、安排随访 |
### 1.2 涉及的技术
| 技术 | 是什么 | 在本项目中的用途 |
|------|--------|------------------|
| **.NET 10** | 微软的后端开发框架 | 写 API 接口,处理业务逻辑 |
| **C#** | .NET 的编程语言 | 所有后端代码都用 C# 写 |
| **Entity Framework Core (EF Core)** | .NET 的数据库操作框架 | 用 C# 对象操作数据库,不用手写 SQL |
| **PostgreSQL** | 开源关系型数据库 | 存储用户、健康数据、药物、咨询等所有数据 |
| **Npgsql** | PostgreSQL 的 .NET 驱动 | 让 EF Core 能连接和操作 PostgreSQL |
| **JWT (JSON Web Token)** | 一种身份验证令牌 | 用户登录后,凭令牌访问 API不用每次输密码 |
| **SignalR** | 微软的实时通信框架 | 实现医生和患者之间的实时聊天 |
| **Swagger** | API 文档工具 | 自动生成 API 文档页面,可以直接在浏览器里测试接口 |
| **MinIO** | S3 兼容的对象存储 | 存储图片(报告照片、头像等) |
| **Redis** | 内存缓存数据库 | 缓存常用数据,加速访问 |
### 1.3 项目文件结构
```
backend/
├── HealthManager.slnx ← 解决方案文件
├── src/
│ ├── HealthManager.Domain/ ← 领域层(实体 + 接口)
│ ├── HealthManager.Application/ ← 应用层(服务 + DTO
│ ├── HealthManager.Infrastructure/← 基础设施层(数据库 + JWT
│ └── HealthManager.WebApi/ ← Web API 层(控制器 + 启动)
├── start-dev.bat ← 一键启动脚本
└── data/
├── pgdata/ ← PostgreSQL 数据文件
└── minio/ ← MinIO 文件存储
```
---
## 2. 架构设计:什么是整洁架构
### 2.1 为什么分成四层?
传统的写法是把所有代码写在一起比如把数据库查询、业务逻辑、API 接口写在一个文件里)。这样写久了会越来越乱,改一个地方可能影响其他功能。
**整洁架构Clean Architecture** 的核心思想是:**把代码按照职责分成不同的层,内层的代码不依赖外层的代码**。
```
┌──────────────────────────────────────────┐
│ WebApi (接口层) │ ← 接收 HTTP 请求,返回 JSON
│ 接收请求 → 调用服务 → 返回结果 │
├──────────────────────────────────────────┤
│ Application (应用层) │ ← 业务逻辑、数据转换
│ 具体的业务操作:登录、查健康数据等 │
├──────────────────────────────────────────┤
│ Domain (领域层) │ ← 核心:定义实体和接口
│ 实体类User、HealthRecord...+ 接口 │
├──────────────────────────────────────────┤
│ Infrastructure (基础设施层) │ ← 和外部系统打交道
│ 数据库操作、JWT 生成、发邮件等 │
└──────────────────────────────────────────┘
```
**依赖方向**WebApi → Application → Domain ← Infrastructure
- 所有层都依赖 Domain因为 Domain 定了"数据长什么样"
- Infrastructure 实现了 Domain 定义的接口
### 2.2 每一层的职责
| 层 | 能做什么 | 不能做什么 |
|----|----------|-----------|
| **Domain** | 定义实体、定义接口 | 不写具体逻辑、不访问数据库 |
| **Application** | 写业务逻辑、定义 DTO | 不直接访问 HTTP 请求、不直接操作数据库 |
| **Infrastructure** | 实现接口、操作数据库、生成 JWT | 不处理 HTTP 请求 |
| **WebApi** | 接收 HTTP 请求、控制权限、返回响应 | 不写业务逻辑、不直接操作数据库 |
---
## 3. Domain 领域层
> 这是最核心的一层,定义了"数据长什么样"。就像一个房子的地基。
### 3.1 接口 `Interfaces/IJwtProvider.cs`
```csharp
namespace HealthManager.Domain.Interfaces;
public interface IJwtProvider
{
string GenerateAccessToken(Guid userId, string name, string role);
string GenerateRefreshToken();
}
```
定义了一个"契约"——任何实现这个接口的类,必须提供两个方法:
- `GenerateAccessToken`生成访问令牌30分钟有效
- `GenerateRefreshToken`:生成刷新令牌(用来换新的访问令牌)
**为什么放在 Domain 层?** 因为 Application 层需要使用这个功能,但 Application 层不应该知道具体是怎么实现的。所以 Domain 定义接口Infrastructure 去实现。
### 3.2 实体类一览
每种实体对应数据库中的一张表。
| 实体文件 | 对应表 | 用途 |
|----------|--------|------|
| `User.cs` | Users | 用户(患者+医生),通过 Role 字段区分 |
| `RefreshToken.cs` | RefreshTokens | JWT 刷新令牌 |
| `Device.cs` | Devices | 智能设备绑定 |
| `HealthRecord.cs` | HealthRecords | 健康测量数据(血压/心率/血糖等Value 用 JSON 存储 |
| `DietRecord.cs` | DietRecords | 饮食记录 |
| `ExerciseRecord.cs` | ExerciseRecords | 运动记录 |
| `Medication.cs` | Medications | 药物方案 |
| `MedicationRecord.cs` | MedicationRecords | 服药打卡记录 |
| `Consultation.cs` | Consultations | 医患咨询会话 |
| `ConsultationMessage.cs` | ConsultationMessages | 咨询消息 |
| `QuickReplyTemplate.cs` | QuickReplyTemplates | 医生快捷回复模板 |
| `Report.cs` | Reports | 检查报告 |
| `ReportItem.cs` | ReportItems | 报告中的检查项目 |
| `FollowUp.cs` | FollowUps | 随访计划 |
| `Notification.cs` | Notifications | 通知 |
### 3.3 关键实体详解
#### User用户表
患者和医生共用一张 `Users` 表,通过 `Role` 字段区分:
- 患者独有字段:`MedicalHistory`, `StentDate`, `StentType`, `HeightCm`, `WeightKg`
- 医生独有字段:`Department`, `Title`, `Specialty`, `Introduction`, `IsAvailable`
- 公共字段:`Id`, `Phone`, `PasswordHash`, `Name`, `Gender`, `Birthday`, `CreatedAt`
- 软删除:`IsDeleted` + `DeletedAt`
#### HealthRecord健康记录
| 属性 | 类型 | 说明 |
|------|------|------|
| Type | string | `blood_pressure`, `heart_rate`, `blood_sugar`, `spo2`, `weight`, `steps` |
| Value | JsonDocument | **关键设计**:不同测量存不同 JSON 格式。血压 `{systolic, diastolic, pulse}`,心率 `{value}` |
| Unit | string | 单位mmHg, bpm, mmol/L, %, kg, 步 |
| Source | string | 来源manual手动, device设备, doctor医生 |
#### Medication药物
| 属性 | 类型 | 说明 |
|------|------|------|
| DrugName | string | 药物名称 |
| Dosage | string | 剂量,如 "100mg" |
| Frequency | string | 频率,如 "每日1次" |
| TimeSlots | List\<string\> | 服药时间点,如 ["08:00", "20:00"],存为 PostgreSQL text[] |
| Status | string | active / completed / stopped |
#### Report报告
| 属性 | 类型 | 说明 |
|------|------|------|
| Category | string | 血液检查 / 心电图 / 影像学 / 尿液检查 |
| ImageUrls | List\<string\> | 报告图片地址列表 |
| Status | string | pending待审核→ interpreting解读中→ completed已完成 |
| RiskLevel | string? | normal / attention / abnormal |
---
## 4. Application 应用层
> 这一层写所有的**业务逻辑**。
### 4.1 DTO数据传输对象
DTO = Data Transfer Object。用于前后端之间传输数据而不是直接把数据库实体暴露给前端。
| 文件 | 包含的 DTO |
|------|-----------|
| `DTOs/AuthDtos.cs` | LoginRequest, RegisterRequest, SendSmsRequest, AuthResponse, UserProfileResponse, UpdateProfileRequest |
| `DTOs/ConsultationDtos.cs` | ConsultationCreateRequest, SendMessageRequest 等 |
| `DTOs/FollowUpDtos.cs` | FollowUpCreateRequest, FollowUpUpdateRequest, FollowUpResponse |
| `DTOs/HealthDtos.cs` | HealthRecordCreateRequest, HealthRecordResponse, HealthStatsResponse |
| `DTOs/MedicationDtos.cs` | MedicationCreateRequest, MedicationResponse, AdherenceResponse |
| `DTOs/ReportDtos.cs` | ReportUploadRequest, ReportInterpretRequest, ReportResponse |
### 4.2 服务类
每个服务类负责一个业务领域的所有逻辑。通过构造函数注入 `AppDbContext`
| 服务文件 | 负责领域 | 核心方法 |
|----------|---------|---------|
| `AuthService.cs` | 用户认证 | GetUserByPhone, SaveRefreshToken, RevokeRefreshToken, HashPassword |
| `HealthService.cs` | 健康数据 | GetRecords, GetLatest, AddRecord, GetStats含趋势计算 |
| `MedicationService.cs` | 药物管理 | GetUserMedications, Add, MarkTaken, GetAdherenceRate |
| `ConsultationService.cs` | 在线问诊 | GetDoctors, Start, SendMessage, GetMessages |
| `PatientService.cs` | 患者管理(医生用) | GetPatients支持搜索分页, GetPatientDetail |
| `ReportService.cs` | 报告管理 | Upload, GetPatientReports, GetAllReports, Interpret |
| `FollowUpService.cs` | 随访管理 | Add, GetPatientFollowUps, GetDoctorFollowUps, Update |
| `NotificationService.cs` | 通知 | GetUserNotifications, GetUnreadCount, MarkAsRead, Create |
#### 关键方法详解
**HealthService.GetStatsAsync**:计算每种健康类型的统计值
- 查最近 7 天记录算周平均 → 和前面 7 天比较 → 判断趋势(上升 >3%、下降 <3%、稳定
- 查全部记录算月平均
- 支持的类型blood_pressure, heart_rate, blood_sugar, spo2, weight, steps
**MedicationService.GetAdherenceRateAsync**计算依从率
- 依从率 = 实际服用次数 / 总应有次数 × 100%
- 30 天内按药物 TimeSlots 数量算总应服药次数
**MedicationService.MarkTakenAsync**标记已服药
- 先查今天该时间点是否已有记录幂等防止重复打卡
- 有则更新无则新建
---
## 5. Infrastructure 基础设施层
> 这一层和"外部世界"打交道数据库、JWT 加密等具体技术实现。
### 5.1 `Data/AppDbContext.cs` — 数据库上下文
**整个项目中最重要的基础设施文件**EF Core "总管家"。
**做了什么**
1. **定义 15 个 DbSet**每个 DbSet 对应一张数据库表
2. **在 `OnModelCreating` 中配置**
- JSON `HealthRecord.Value` PostgreSQL `jsonb` 类型
- 数组列`List<string>` PostgreSQL `text[]` 类型
- 索引Phone唯一)、RoleUserId+TypeStatusScheduledAt
- 外键关系用户健康记录用户药物咨询消息
### 5.2 `Data/DataSeeder.cs` — 种子数据
在数据库首次创建时自动插入演示数据
| 数据 | 数量 | 说明 |
|------|------|------|
| 患者 | 2 | 张明60岁PCI术后)、李芳53岁PCI术后 |
| 医生 | 2 | 王建国主任医师)、陈雪梅副主任医师 |
| 健康记录 | ~93 | 患者1 31 天血压+心率+体重数据 |
| 药物 | 4 | 阿司匹林替格瑞洛瑞舒伐他汀美托洛尔 |
| 服药记录 | ~50 | 8 天服药情况 90% 依从率 |
| 咨询 | 1 | 张明咨询王建国 |
| 消息 | 4 | 完整医患对话 |
| 通知 | 4 | 用药提醒复查提醒等 |
**关键技术点**
- 演示密码 `demo123`SHA256 哈希存储
- 固定随机种子 `new Random(42)`每次数据一致
- **所有时间必须是 UTC**`new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Utc)`
### 5.3 `Services/JwtProvider.cs` — JWT 令牌提供者
实现了 `IJwtProvider` 接口
- `GenerateAccessToken`HMAC-SHA256 签名30 分钟过期包含 userId + name + role
- `GenerateRefreshToken`加密随机数生成器产生 64 字节 Base64 字符串
---
## 6. WebApi 接口层
### 6.1 `Program.cs` — 应用程序入口
**整个后端启动的唯一入口**按顺序配置
1. **注册数据库**EF Core + PostgreSQL连接字符串从 `appsettings.json` 读取
2. **注册 JWT 认证**验证规则密钥签发者过期时间
3. **注册 Swagger**API 文档页面
4. **注册 SignalR**实时通信
5. **注册 CORS**允许前端跨域开发阶段允许 localhost:5173-5175
6. **注册业务服务**AuthServiceHealthService
7. **配置中间件管道**CORS 认证 授权 控制器 SignalR Hub
8. **初始化数据库**`EnsureCreatedAsync` + `SeedAsync`
### 6.2 `Hubs/ChatHub.cs` — SignalR 实时聊天
- `JoinConsultation(consultationId)`加入聊天室
- `LeaveConsultation(consultationId)`离开
- `SendMessage(consultationId, content)`发送消息 广播给聊天室所有人
### 6.3 API 接口完整列表
#### 认证AuthController- `api/auth`
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/send-sms` | POST | 公开 | 发送短信验证码演示版直接返回成功 |
| `/login` | POST | 公开 | 手机号+验证码登录 返回 JWT 令牌对 |
| `/register` | POST | 公开 | 注册新患者 |
| `/refresh` | POST | 公开 | 刷新令牌换新的访问令牌 |
| `/me` | GET | 需登录 | 获取当前用户完整资料 |
| `/me` | PUT | 需登录 | 更新个人资料姓名/性别/身高/体重/病史 |
#### 健康数据HealthController- `api/health-records`
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/` | GET | 需登录 | 查询记录支持 `?type=&days=`医生可加 `?patientId=` |
| `/stats` | GET | 需登录 | 所有类型的统计最新值+周均+月均+趋势 |
| `/latest/{type}` | GET | 需登录 | 某类型最新一条 |
| `/` | POST | 需登录 | 添加健康记录 |
#### 药物管理MedicationController- `api/medications`
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/` | GET | 需登录 | 列出所有药物医生可加 `?patientId=` |
| `/` | POST | 需登录 | 添加新药物 |
| `/{id}` | GET | 需登录 | 药物详情 |
| `/{id}/records` | GET | 需登录 | 服药记录列表 |
| `/{id}/take` | POST | 需登录 | 标记已服药 `{ timeSlot: "08:00" }` |
| `/{id}/adherence` | GET | 需登录 | 依从率百分比 |
#### 在线问诊ConsultationController- `api/consultations`
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/doctors` | GET | 需登录 | 可接诊的医生列表 |
| `/` | GET | 需登录 | 我的咨询列表 |
| `/` | POST | 需登录 | 发起新咨询 |
| `/{id}/messages` | GET | 需登录 | 咨询消息列表 |
| `/{id}/messages` | POST | 需登录 | 发送消息 |
#### 报告管理ReportController- `api/reports`
| 端点 | 方法 | 认证 | 权限 | 说明 |
|------|------|------|------|------|
| `/` | GET | 需登录 | | 患者看自己医生看全部 |
| `/pending` | GET | 需登录 | 仅医生 | 待审核报告 |
| `/{id}` | GET | 需登录 | | 报告详情 |
| `/` | POST | 需登录 | | 上传报告 `{title, category, imageUrls}` |
| `/{id}/interpret` | POST | 需登录 | 仅医生 | 提交解读 `{summary, items, riskLevel, suggestions}` |
#### 随访管理FollowUpController- `api/follow-ups`
| 端点 | 方法 | 认证 | 权限 | 说明 |
|------|------|------|------|------|
| `/` | GET | 需登录 | | 患者看自己医生看分配给自己 |
| `/{id}` | GET | 需登录 | | 随访详情 |
| `/` | POST | 需登录 | | 创建随访 |
| `/{id}` | PUT | 需登录 | 仅医生 | 更新随访 |
#### 患者管理PatientController- `api/patients`(仅医生)
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/` | GET | 仅医生 | 患者列表支持 `?search=&page=&pageSize=` |
| `/{id}` | GET | 仅医生 | 患者详情 |
#### 通知NotificationController- `api/notifications`
| 端点 | 方法 | 认证 | 说明 |
|------|------|------|------|
| `/` | GET | 需登录 | 通知列表 |
| `/unread-count` | GET | 需登录 | 未读数量 |
| `/{id}/read` | PUT | 需登录 | 标记已读 |
| `/read-all` | PUT | 需登录 | 全部已读 |
### 6.4 配置文件
| 文件 | 内容 |
|------|------|
| `appsettings.json` | PostgreSQL 连接串JWT 密钥/签发者Redis 连接MinIO 连接 |
| `appsettings.Development.json` | 开发环境覆盖配置 |
| `Properties/launchSettings.json` | 启动配置端口 5000Development 环境自动开 Swagger |
---
## 7. 数据库表结构
### 7.1 所有表一览
| 表名 | 用途 | 主要查询场景 |
|------|------|-------------|
| `Users` | 用户患者+医生+管理员 | 按手机号查按角色查按姓名搜索 |
| `RefreshTokens` | JWT 刷新令牌 | 按令牌字符串查按用户查 |
| `Devices` | 智能设备 | 按用户查绑定的设备 |
| `HealthRecords` | 健康测量数据 | 按用户+类型查按时间范围查取最新值 |
| `DietRecords` | 饮食记录 | 按用户+日期查 |
| `ExerciseRecords` | 运动记录 | 按用户+日期查 |
| `Medications` | 药物方案 | 按用户查按状态查按医生查 |
| `MedicationRecords` | 服药记录 | 按药物查按用户+时间查 |
| `Consultations` | 咨询会话 | 按患者查按医生查按状态查 |
| `ConsultationMessages` | 咨询消息 | 按咨询查按发送者查 |
| `QuickReplyTemplates` | 快捷回复模板 | 按医生查 |
| `Reports` | 检查报告 | 按患者查按状态查 |
| `ReportItems` | 报告检查项 | 按报告查 |
| `FollowUps` | 随访计划 | 按患者查按医生查按计划时间查 |
| `Notifications` | 通知 | 按用户查按已读/未读查 |
### 7.2 重要索引
| | 索引 | 加速的查询 |
|----|------|-----------|
| `Users` | Phone (唯一) | 登录时按手机号查找 |
| `Users` | Role | 列出所有医生/患者 |
| `HealthRecords` | UserId + Type | 查某人的血压数据 |
| `HealthRecords` | RecordedAt | 按时间排序 |
| `Medications` | UserId, Status | 查药物和状态 |
| `MedicationRecords` | UserId + CreatedAt | 查最近服药记录 |
| `Consultations` | Status, PatientId, DoctorId | 查活跃咨询 |
| `Reports` | Status | 查待审核报告 |
| `FollowUps` | ScheduledAt | 按预约时间排序 |
| `Notifications` | UserId + IsRead | 查未读通知 |
### 7.3 特殊数据类型
| PostgreSQL 类型 | 对应 C# 类型 | 使用场景 |
|-----------------|-------------|---------|
| `uuid` | `Guid` | 所有主键 |
| `jsonb` | `JsonDocument` | HealthRecord.Value |
| `text[]` | `List<string>` | MedicalHistoryTimeSlotsImageUrls |
| `timestamp with time zone` | `DateTime` (UTC) | 所有时间戳 |
| `date` | `DateOnly` | 出生日期开始日期 |
---
## 8. 常见问题 FAQ
### Q1: 为什么所有 ID 都用 Guid 而不是自增数字?
- **安全**自增 ID 会暴露数据量
- **分布式友好**多台服务器不会冲突
- **前端可预生成**不需要等数据库返回 ID
### Q2: 为什么患者和医生放在同一张 Users 表?
- 登录逻辑完全一样简化设计
- 通过 `Role` 字段区分权限
- "不用的字段留空"处理差异
### Q3: JWT 令牌过期了怎么办?
Access Token 30 分钟过期 前端用 Refresh Token 换新的 Access TokenRefresh Token 7 天后也过期 需重新登录
### Q4: 健康数据的 Value 为什么用 JSON
不同测量类型数据结构不同血压 `{systolic, diastolic, pulse}`心率 `{value}`JSON 列提供灵活性PostgreSQL jsonb 支持索引和查询
### Q5: 如何重置数据库?
```sql
-- 连接到 PostgreSQL
DROP DATABASE "HealthManager";
```
重启后端EF Core 自动重建表DataSeeder 自动插入演示数据
### Q6: 数据库连接信息
- 数据库名`HealthManager`
- 用户名`postgres`
- 密码`postgres123`
- 端口`5432`
- 数据目录`D:\APP\data\pgdata\`
### Q7: 为什么 DateTime 必须用 UTC
PostgreSQL `timestamp with time zone` 列类型要求传入的时间必须是 `DateTimeKind.Utc`如果用 `DateTimeKind.Unspecified` `DateTimeKind.Local`Npgsql 会抛异常解决办法`DateTime.SpecifyKind(dt, DateTimeKind.Utc)`
---
> 文档版本v1.0 | 最后更新2026-05-20 | 后端技术文档