Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR Frontend patient: React 19 + TypeScript + Vite (mobile H5) Frontend doctor: React 19 + TypeScript + Vite (desktop web)
1518 lines
62 KiB
Markdown
1518 lines
62 KiB
Markdown
# HealthManager 健康管理平台 — 超级详细技术文档
|
||
|
||
> **适用对象**:小白开发者 / 新加入项目的同学
|
||
> **文档目标**:看懂每一个文件是干什么的,代码在做什么,为什么要这样写
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [项目总览](#1-项目总览)
|
||
2. [架构设计:什么是"整洁架构"](#2-架构设计什么是整洁架构)
|
||
3. [后端 — Domain 领域层](#3-后端--domain-领域层)
|
||
4. [后端 — Application 应用层](#4-后端--application-应用层)
|
||
5. [后端 — Infrastructure 基础设施层](#5-后端--infrastructure-基础设施层)
|
||
6. [后端 — WebApi 接口层](#6-后端--webapi-接口层)
|
||
7. [病人端前端 (frontend-patient)](#7-病人端前端-frontend-patient)
|
||
8. [医生端前端 (frontend-doctor)](#8-医生端前端-frontend-doctor)
|
||
9. [数据库表结构](#9-数据库表结构)
|
||
10. [常见问题 FAQ](#10-常见问题-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** | 内存缓存数据库 | 缓存常用数据,加速访问 |
|
||
| **React** | Facebook 出的前端框架 | 构建用户界面,组件化开发 |
|
||
| **TypeScript** | JavaScript 的超集,加了类型系统 | 让前端代码更安全、更好维护 |
|
||
| **Vite** | 新一代前端构建工具 | 开发时热更新快,打包也快 |
|
||
| **Zustand** | 轻量级状态管理库 | 管理登录状态、用户信息等全局数据 |
|
||
| **React Router** | React 路由库 | 实现页面跳转,URL 和页面组件对应 |
|
||
| **ECharts** | 百度出品的数据可视化库 | 画趋势图、饼图,展示健康数据变化 |
|
||
|
||
### 1.3 项目文件结构
|
||
|
||
```
|
||
D:\APP\
|
||
├── backend\ ← 后端 .NET 项目
|
||
│ ├── HealthManager.slnx ← 解决方案文件(管理多个项目)
|
||
│ ├── start-dev.bat ← 一键启动脚本
|
||
│ └── src\
|
||
│ ├── HealthManager.Domain\ ← 领域层(实体 + 接口)
|
||
│ ├── HealthManager.Application\← 应用层(服务 + DTO)
|
||
│ ├── HealthManager.Infrastructure\← 基础设施层(数据库 + JWT)
|
||
│ └── HealthManager.WebApi\ ← Web API 层(控制器 + 启动)
|
||
├── frontend-patient\ ← 病人 H5 前端
|
||
│ └── src\
|
||
│ ├── pages\ ← 页面组件
|
||
│ ├── services\ ← API 调用函数
|
||
│ ├── stores\ ← 状态管理
|
||
│ ├── components\ ← 公共组件
|
||
│ ├── types\ ← 类型定义
|
||
│ ├── utils\ ← 工具函数
|
||
│ └── router\ ← 路由配置
|
||
├── frontend-doctor\ ← 医生 PC 前端
|
||
│ └── src\
|
||
│ ├── pages\ ← 页面组件
|
||
│ ├── services\ ← API 调用函数
|
||
│ ├── stores\ ← 状态管理
|
||
│ ├── types\ ← 类型定义
|
||
│ └── router\ ← 路由配置
|
||
└── 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 定义的接口(比如 `IJwtProvider`)
|
||
|
||
### 2.2 每一层的职责
|
||
|
||
| 层 | 能做什么 | 不能做什么 |
|
||
|----|----------|-----------|
|
||
| **Domain** | 定义实体、定义接口 | 不写具体逻辑、不访问数据库 |
|
||
| **Application** | 写业务逻辑、定义 DTO | 不直接访问 HTTP 请求、不直接操作数据库 |
|
||
| **Infrastructure** | 实现接口、操作数据库、生成 JWT | 不处理 HTTP 请求 |
|
||
| **WebApi** | 接收 HTTP 请求、控制权限、返回响应 | 不写业务逻辑、不直接操作数据库 |
|
||
|
||
---
|
||
|
||
## 3. 后端 — Domain 领域层
|
||
|
||
> 这是最核心的一层,定义了"数据长什么样"。就像一个房子的地基。
|
||
|
||
### 3.1 项目文件 `HealthManager.Domain.csproj`
|
||
|
||
这是 .NET 项目的配置文件,告诉编译器:
|
||
- 这是一个类库项目(不是可运行的程序)
|
||
- 使用 .NET 10
|
||
- 引用了哪些 NuGet 包(这里没有额外依赖,是最纯净的)
|
||
|
||
### 3.2 接口 `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 层不应该知道具体是怎么实现的(可能是 JWT,也可能是其他方式)。所以 Domain 定义接口,Infrastructure 去实现。
|
||
|
||
### 3.3 实体类
|
||
|
||
实体类 = 数据库表的 C# 映射。每个实体对应数据库中的一张表,类的属性对应表的列。
|
||
|
||
---
|
||
|
||
#### `Entities/User.cs` — 用户表
|
||
|
||
**对应数据库表**:`Users`
|
||
|
||
**属性说明**:
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 唯一标识,全局不重复的 ID(不用自增数字,避免暴露用户量) |
|
||
| Role | string | 角色:`"patient"`(患者)、`"doctor"`(医生)、`"admin"`(管理员) |
|
||
| Phone | string | 手机号,用于登录(相当于用户名) |
|
||
| PasswordHash | string | 密码的 SHA256 哈希值,不存明文 |
|
||
| Name | string | 真实姓名 |
|
||
| AvatarUrl | string? | 头像图片地址(可空) |
|
||
| Gender | string? | 性别 |
|
||
| Birthday | DateOnly? | 出生日期 |
|
||
| HeightCm | decimal? | 身高(厘米) |
|
||
| WeightKg | decimal? | 体重(公斤) |
|
||
| MedicalHistory | List\<string\>? | 病史列表,如 `["高血压", "冠心病"]`,在数据库存为数组 |
|
||
| StentDate | DateOnly? | 支架植入日期(患者特有) |
|
||
| StentType | string? | 支架类型(患者特有) |
|
||
| Department | string? | 科室(医生特有) |
|
||
| Title | string? | 职称,如 `"主任医师"`(医生特有) |
|
||
| Specialty | List\<string\>? | 擅长领域列表(医生特有) |
|
||
| Introduction | string? | 医生简介(医生特有) |
|
||
| IsAvailable | bool | 是否可接诊(医生特有) |
|
||
| CreatedAt | DateTime | 账号创建时间 |
|
||
| UpdatedAt | DateTime | 最后更新时间 |
|
||
| IsDeleted | bool | 软删除标记(不真删数据) |
|
||
| DeletedAt | DateTime? | 删除时间 |
|
||
|
||
**设计特点**:患者和医生共用一张 `Users` 表,通过 `Role` 字段区分。患者独有的字段(如病史、支架日期)对医生为空;医生独有的字段(如科室、职称)对患者为空。这叫"单表继承"模式。
|
||
|
||
**导航属性**:User 还包含多个"一对多"关系,表示一个用户可以有多个健康记录、多种药物等。
|
||
|
||
---
|
||
|
||
#### `Entities/RefreshToken.cs` — 刷新令牌表
|
||
|
||
**对应数据库表**:`RefreshTokens`
|
||
|
||
**干什么的?** 用户登录后,后端发两个令牌:
|
||
- **Access Token**:短期有效(30分钟),用于访问 API
|
||
- **Refresh Token**:长期有效(7天),用于在 Access Token 过期后换新的
|
||
|
||
**属性说明**:
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 属于哪个用户 |
|
||
| Token | string | 令牌字符串(唯一的) |
|
||
| ExpiresAt | DateTime | 过期时间 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
| RevokedAt | DateTime? | 吊销时间(如果被吊销了就不能再用了) |
|
||
|
||
---
|
||
|
||
#### `Entities/Device.cs` — 设备表
|
||
|
||
**对应数据库表**:`Devices`
|
||
|
||
**干什么的?** 患者可以绑定智能设备(血压计、手环等),设备数据自动同步。
|
||
|
||
**属性说明**:
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid? | 绑定的用户(可空,未绑定时为空) |
|
||
| Name | string | 设备名称,如 `"欧姆龙血压计"` |
|
||
| Type | string | 设备类型:血压计、手环、血糖仪、体重秤 |
|
||
| MacAddress | string? | MAC 地址 |
|
||
| SerialNumber | string? | 序列号 |
|
||
| IsBound | bool | 是否已绑定用户 |
|
||
| BoundAt | DateTime? | 绑定时间 |
|
||
| LastSyncAt | DateTime? | 最后同步时间 |
|
||
| BatteryLevel | int? | 电量百分比 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/HealthRecord.cs` — 健康记录表
|
||
|
||
**对应数据库表**:`HealthRecords`
|
||
|
||
**干什么的?** 存储用户的健康测量数据(血压、心率、血糖、血氧、体重、步数)。
|
||
|
||
**属性说明**:
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 属于哪个用户 |
|
||
| Type | string | 类型:`blood_pressure`(血压)、`heart_rate`(心率)、`blood_sugar`(血糖)、`spo2`(血氧)、`weight`(体重)、`steps`(步数) |
|
||
| Value | JsonDocument | **关键设计**:不同测量的数据格式不一样。血压是 `{systolic:120, diastolic:80, pulse:72}`,心率是 `{value:72}`。用 JSON 列存储,灵活适应不同格式 |
|
||
| Unit | string | 单位:`mmHg`、`bpm`、`mmol/L`、`%`、`kg`、`步` |
|
||
| RecordedAt | DateTime | 测量时间 |
|
||
| Source | string | 数据来源:`manual`(手动输入)、`device`(设备同步)、`doctor`(医生录入) |
|
||
| Notes | string? | 备注 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
**为什么 Value 用 JSON 而不是分别建列?** 因为不同测量类型的数据结构不同。如果每种类型建一张表,会有很多表。JSON 列让数据库结构简单,同时保持灵活性。
|
||
|
||
---
|
||
|
||
#### `Entities/DietRecord.cs` — 饮食记录表
|
||
|
||
**对应数据库表**:`DietRecords`
|
||
|
||
**干什么的?** 记录患者的饮食情况。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 属于哪个用户 |
|
||
| MealType | string | 餐类型:`breakfast`、`lunch`、`dinner`、`snack` |
|
||
| FoodName | string | 食物名称 |
|
||
| Portion | string? | 份量描述 |
|
||
| Calories | int? | 卡路里估算 |
|
||
| Notes | string? | 备注 |
|
||
| RecordedAt | DateOnly | 记录日期 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/ExerciseRecord.cs` — 运动记录表
|
||
|
||
**对应数据库表**:`ExerciseRecords`
|
||
|
||
**干什么的?** 记录患者的运动情况。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 属于哪个用户 |
|
||
| ExerciseType | string | 运动类型:`步行`、`慢跑`、`游泳`、`骑行` |
|
||
| DurationMin | int | 运动时长(分钟) |
|
||
| Intensity | string? | 强度:`low`、`moderate`、`high` |
|
||
| CaloriesBurned | int? | 消耗卡路里 |
|
||
| Notes | string? | 备注 |
|
||
| RecordedAt | DateOnly | 记录日期 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/Medication.cs` — 药物表
|
||
|
||
**对应数据库表**:`Medications`
|
||
|
||
**干什么的?** 存储医生为患者开的药。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 患者 ID |
|
||
| DoctorId | Guid? | 开药医生 ID |
|
||
| DrugName | string | 药物名称,如 `"阿司匹林肠溶片"` |
|
||
| Dosage | string | 剂量,如 `"100mg"` |
|
||
| Frequency | string | 频率,如 `"每日1次"` |
|
||
| TimeSlots | List\<string\> | 服药时间点列表,如 `["08:00", "20:00"]`,存为数组 |
|
||
| StartDate | DateOnly | 开始日期 |
|
||
| EndDate | DateOnly? | 结束日期(为空表示长期服用) |
|
||
| Notes | string? | 备注(如注意事项) |
|
||
| Status | string | 状态:`active`(服用中)、`completed`(已结束)、`stopped`(已停用) |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
| UpdatedAt | DateTime | 更新时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/MedicationRecord.cs` — 服药记录表
|
||
|
||
**对应数据库表**:`MedicationRecords`
|
||
|
||
**干什么的?** 记录每一次服药情况(吃了还是没吃)。用来计算"依从率"(有没有按时吃药)。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| MedicationId | Guid | 属于哪个药物 |
|
||
| UserId | Guid | 患者 ID |
|
||
| TimeSlot | string | 哪个时间点,如 `"08:00"` |
|
||
| TakenAt | DateTime? | 实际服药时间(为空表示没吃) |
|
||
| IsTaken | bool | 是否已服用 |
|
||
| SkippedReason | string? | 未服用的原因,如 `"忘记"` |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/Consultation.cs` — 咨询表
|
||
|
||
**对应数据库表**:`Consultations`
|
||
|
||
**干什么的?** 存储一次医患在线咨询会话。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| PatientId | Guid | 患者 ID |
|
||
| DoctorId | Guid | 医生 ID |
|
||
| Subject | string? | 咨询主题 |
|
||
| Status | string | 状态:`active`(进行中)、`closed`(已结束) |
|
||
| StartedAt | DateTime | 开始时间 |
|
||
| ClosedAt | DateTime? | 关闭时间 |
|
||
| ClosedBy | Guid? | 谁关闭的 |
|
||
| Summary | string? | 总结 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/ConsultationMessage.cs` — 咨询消息表
|
||
|
||
**对应数据库表**:`ConsultationMessages`
|
||
|
||
**干什么的?** 存储咨询中的每一条消息。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| ConsultationId | Guid | 属于哪个咨询 |
|
||
| SenderId | Guid | 发送者 ID |
|
||
| SenderRole | string | 发送者角色:`patient`、`doctor`、`system` |
|
||
| ContentType | string | 内容类型:`text`、`image`、`file`、`template` |
|
||
| Content | string | 消息正文 |
|
||
| ImageUrl | string? | 图片地址 |
|
||
| FileUrl | string? | 文件地址 |
|
||
| IsRead | bool | 对方是否已读 |
|
||
| CreatedAt | DateTime | 发送时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/QuickReplyTemplate.cs` — 快捷回复模板表
|
||
|
||
**对应数据库表**:`QuickReplyTemplates`
|
||
|
||
**干什么的?** 医生可以设置常用回复模板,问诊时一键发送,提高效率。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| DoctorId | Guid | 医生 ID |
|
||
| Title | string | 模板标题 |
|
||
| Content | string | 模板内容 |
|
||
| Category | string? | 分类 |
|
||
| SortOrder | int | 排序 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/Report.cs` — 报告表
|
||
|
||
**对应数据库表**:`Reports`
|
||
|
||
**干什么的?** 患者上传的检查报告(化验单、心电图等),由医生审核解读。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| PatientId | Guid | 患者 ID |
|
||
| DoctorId | Guid? | 解读医生 ID |
|
||
| Title | string | 报告标题 |
|
||
| Category | string | 类别:`血液检查`、`心电图`、`影像学`、`尿液检查` |
|
||
| ImageUrls | List\<string\> | 报告图片地址列表,存为数组 |
|
||
| Status | string | 状态:`pending`(待审核)、`interpreting`(解读中)、`completed`(已完成) |
|
||
| RiskLevel | string? | 风险等级:`normal`、`attention`、`abnormal` |
|
||
| Summary | string? | 解读总结 |
|
||
| Suggestions | string? | 医生建议 |
|
||
| UploadedBy | Guid | 上传者 ID |
|
||
| UploadedAt | DateTime | 上传时间 |
|
||
| CompletedAt | DateTime? | 完成时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/ReportItem.cs` — 报告项目表
|
||
|
||
**对应数据库表**:`ReportItems`
|
||
|
||
**干什么的?** 一份报告包含多个检查项目(比如一份化验单有10项指标)。每个项目存一条。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| ReportId | Guid | 属于哪个报告 |
|
||
| ItemName | string | 项目名称,如 `"低密度脂蛋白胆固醇"` |
|
||
| ResultValue | string | 检查结果,如 `"3.2"` |
|
||
| Unit | string? | 单位,如 `"mmol/L"` |
|
||
| ReferenceRange | string? | 正常参考范围,如 `"<3.4"` |
|
||
| IsAbnormal | bool | 是否异常 |
|
||
| SortOrder | int | 排序 |
|
||
|
||
---
|
||
|
||
#### `Entities/FollowUp.cs` — 随访表
|
||
|
||
**对应数据库表**:`FollowUps`
|
||
|
||
**干什么的?** 安排患者复查/随访计划。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| PatientId | Guid | 患者 ID |
|
||
| DoctorId | Guid? | 医生 ID |
|
||
| Title | string | 随访主题 |
|
||
| Description | string? | 描述 |
|
||
| ScheduledAt | DateTime | 计划时间 |
|
||
| Status | string | 状态:`upcoming`(将到来)、`completed`(已完成)、`cancelled`(已取消)、`rescheduled`(已改期) |
|
||
| Notes | string? | 备注 |
|
||
| ReminderEnabled | bool | 是否开启提醒 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
| UpdatedAt | DateTime | 更新时间 |
|
||
|
||
---
|
||
|
||
#### `Entities/Notification.cs` — 通知表
|
||
|
||
**对应数据库表**:`Notifications`
|
||
|
||
**干什么的?** 各类推送通知。
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| Id | Guid | 主键 |
|
||
| UserId | Guid | 接收者 ID |
|
||
| Type | string | 类型:`medication`、`followup`、`consultation`、`report`、`system` |
|
||
| Title | string | 标题 |
|
||
| Content | string | 内容 |
|
||
| RelatedId | Guid? | 关联的业务 ID(比如关联的咨询 ID) |
|
||
| IsRead | bool | 是否已读 |
|
||
| ReadAt | DateTime? | 阅读时间 |
|
||
| CreatedAt | DateTime | 创建时间 |
|
||
|
||
---
|
||
|
||
## 4. 后端 — Application 应用层
|
||
|
||
> 这一层写所有的**业务逻辑**。它不操作数据库(通过 DbContext 操作),不接收 HTTP 请求(由 WebApi 层接收并调用它)。
|
||
|
||
### 4.1 DTO(数据传输对象)
|
||
|
||
DTO = Data Transfer Object。它是"前端和后端之间传输数据用的对象"。
|
||
|
||
**为什么要用 DTO 而不是直接把实体类(Entity)返回给前端?**
|
||
1. **安全**:实体类可能包含密码哈希等敏感数据,DTO 可以过滤掉
|
||
2. **灵活**:前端需要的数据格式可能和数据库结构不一样,DTO 可以自由组合
|
||
3. **解耦**:数据库结构变了,只要调整 DTO 转换逻辑,前端不受影响
|
||
|
||
---
|
||
|
||
#### `DTOs/AuthDtos.cs` — 认证相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `LoginRequest` | 前端发来的登录请求:`{ phone, smsCode }` |
|
||
| `RegisterRequest` | 前端发来的注册请求:`{ phone, smsCode, name }` |
|
||
| `SendSmsRequest` | 发验证码请求:`{ phone }` |
|
||
| `AuthResponse` | 登录成功后返回:`{ userId, name, role, accessToken, refreshToken }` |
|
||
| `UserProfileResponse` | 用户完整资料(患者和医生字段都有) |
|
||
| `UpdateProfileRequest` | 更新个人资料的请求 |
|
||
|
||
---
|
||
|
||
#### `DTOs/ConsultationDtos.cs` — 咨询相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `ConsultationCreateRequest` | 创建咨询:`{ doctorId, subject }` |
|
||
| `ConsultationResponse` | 咨询详情 |
|
||
| `SendMessageRequest` | 发送消息:`{ content, contentType, imageUrl }` |
|
||
| `MessageResponse` | 消息详情 |
|
||
|
||
---
|
||
|
||
#### `DTOs/FollowUpDtos.cs` — 随访相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `FollowUpCreateRequest` | 创建随访:`{ title, description, scheduledAt, reminderEnabled }` |
|
||
| `FollowUpUpdateRequest` | 更新随访(所有字段都可空,只更新提供的字段) |
|
||
| `FollowUpResponse` | 随访详情 |
|
||
|
||
---
|
||
|
||
#### `DTOs/HealthDtos.cs` — 健康记录相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `HealthRecordCreateRequest` | 添加健康记录:`{ type, valueJson, unit, recordedAt, notes }` |
|
||
| `HealthRecordResponse` | 单条健康记录 |
|
||
| `HealthStatsResponse` | 聚合统计:最新值 + 周平均 + 月平均 + 趋势(上升/下降/稳定) |
|
||
|
||
---
|
||
|
||
#### `DTOs/MedicationDtos.cs` — 药物相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `MedicationCreateRequest` | 添加药物:`{ drugName, dosage, frequency, timeSlots, startDate, endDate, notes }` |
|
||
| `MedicationResponse` | 药物详情 |
|
||
| `MedicationRecordResponse` | 服药记录 |
|
||
| `AdherenceResponse` | 依从率:`{ rate(%), takenDays, totalDays }` |
|
||
|
||
---
|
||
|
||
#### `DTOs/ReportDtos.cs` — 报告相关 DTO
|
||
|
||
| DTO | 作用 |
|
||
|-----|------|
|
||
| `ReportUploadRequest` | 上传报告:`{ title, category, imageUrls }` |
|
||
| `ReportInterpretRequest` | 医生解读报告:`{ summary, items, riskLevel, suggestions }` |
|
||
| `ReportResponse` | 报告详情(含解读结果) |
|
||
| `ReportItemDto` | 单个检查项目 |
|
||
|
||
---
|
||
|
||
### 4.2 服务类
|
||
|
||
每个服务类负责一个业务领域的所有逻辑。它们通过构造函数注入 `AppDbContext`(数据库)和需要的其他服务。
|
||
|
||
---
|
||
|
||
#### `Services/AuthService.cs` — 认证服务
|
||
|
||
**负责**:用户登录、注册、Token 刷新、获取资料、更新资料。
|
||
|
||
**主要方法做的事**:
|
||
- **登录**:根据手机号找用户 → 验证密码(演示版跳过) → 生成 JWT 令牌对 → 保存刷新令牌到数据库 → 返回令牌和用户信息
|
||
- **注册**:检查手机号是否已注册 → 创建新用户 → 生成令牌对 → 保存 → 返回
|
||
- **刷新令牌**:找到数据库中的刷新令牌 → 检查是否过期/被吊销 → 生成新令牌对 → 吊销旧令牌 → 保存新令牌
|
||
- **获取资料**:根据用户 ID 查找用户 → 映射成 UserProfileResponse 返回
|
||
- **更新资料**:找到用户 → 更新可修改的字段 → 保存
|
||
|
||
---
|
||
|
||
#### `Services/HealthService.cs` — 健康数据服务
|
||
|
||
**负责**:健康记录的增删查、统计计算。
|
||
|
||
**主要方法做的事**:
|
||
- **获取记录列表**:支持按类型(血压/心率等)、按天数筛选,也可以医生查看指定患者的记录
|
||
- **获取最新记录**:查某类型最新的一条
|
||
- **添加记录**:验证 JSON 格式 → 创建 HealthRecord → 保存
|
||
- **获取统计**:这是最复杂的方法。查出每种类型的所有记录 → 计算最新值、7天平均、30天平均 → 比较趋势(上升/下降/稳定)
|
||
|
||
---
|
||
|
||
#### `Services/MedicationService.cs` — 药物管理服务
|
||
|
||
**负责**:药物的增删查、服药打卡、依从率计算。
|
||
|
||
**主要方法做的事**:
|
||
- **列出药物**:查出用户的所有药物,支持医生查看指定患者的
|
||
- **添加药物**:创建新药物 → 为所有时间段生成服药记录(预生成未来几天的记录)
|
||
- **获取详情**:根据 ID 查询单条药物
|
||
- **获取服药记录**:查某药物的所有服药记录
|
||
- **标记已服用**:用户点"已服药" → 更新对应时间和时间点的服药记录为已服用
|
||
- **计算依从率**:已服用次数 / 总需服用次数 × 100%
|
||
|
||
---
|
||
|
||
#### `Services/ConsultationService.cs` — 咨询问诊服务
|
||
|
||
**负责**:列出医生、创建咨询、收发消息。
|
||
|
||
**主要方法做的事**:
|
||
- **列出可用医生**:查出所有 `Role=doctor` 且 `IsAvailable=true` 的用户
|
||
- **创建咨询**:患者选择医生 → 检查是否已有活跃咨询(有的话复用) → 创建新 Consultation
|
||
- **获取咨询列表**:患者看自己的,医生看分配给自己的
|
||
- **发送消息**:创建 ConsultationMessage → 保存 → 通过 SignalR 实时推送给对方
|
||
- **获取消息**:查某咨询的所有消息,按时间排序
|
||
|
||
---
|
||
|
||
#### `Services/PatientService.cs` — 患者管理服务(医生用)
|
||
|
||
**负责**:医生查看患者列表和详情。
|
||
|
||
**主要方法做的事**:
|
||
- **患者列表**:查出所有 `Role=patient` 的用户,支持按姓名/手机号搜索,支持分页
|
||
- **患者详情**:根据 ID 查出患者完整资料
|
||
|
||
---
|
||
|
||
#### `Services/ReportService.cs` — 报告管理服务
|
||
|
||
**负责**:报告的增删查、医生解读。
|
||
|
||
**主要方法做的事**:
|
||
- **上传报告**:患者上传检查报告图片 → 创建 Report 记录(状态为 pending)
|
||
- **列出报告**:患者看自己的,医生可看所有或筛选特定患者
|
||
- **待审核列表**:列出所有状态为 pending 的报告(医生用)
|
||
- **报告详情**:查询单条报告及其所有 ReportItem
|
||
- **解读报告**:医生提交解读 → 保存总结、风险等级、建议 → 创建所有 ReportItem → 更新报告状态为 completed
|
||
|
||
---
|
||
|
||
#### `Services/FollowUpService.cs` — 随访管理服务
|
||
|
||
**负责**:随访计划的增删改查。
|
||
|
||
**主要方法做的事**:
|
||
- **创建随访**:患者或医生创建随访计划
|
||
- **列出随访**:患者看自己的,医生看分配给自己的
|
||
- **更新随访**:医生修改随访信息(改时间、改状态、加备注)
|
||
|
||
---
|
||
|
||
#### `Services/NotificationService.cs` — 通知服务
|
||
|
||
**负责**:通知的查询、已读标记、创建。
|
||
|
||
**主要方法做的事**:
|
||
- **列出通知**:查出用户所有通知,按时间倒序
|
||
- **未读计数**:统计 `IsRead=false` 的通知数量
|
||
- **标记已读**:单条或全部标为已读
|
||
- **创建通知**:其他服务调用此方法创建通知(如"医生回复了你的咨询")
|
||
|
||
---
|
||
|
||
## 5. 后端 — Infrastructure 基础设施层
|
||
|
||
> 这一层和"外部世界"打交道:数据库、JWT 加密等具体技术实现。
|
||
|
||
### 5.1 `Data/AppDbContext.cs` — 数据库上下文
|
||
|
||
**这是整个项目中最重要的基础设施文件**。它是 EF Core 的"总管家"。
|
||
|
||
**做了什么**:
|
||
1. **定义 15 个 DbSet**:每个 DbSet 对应一张数据库表
|
||
```csharp
|
||
public DbSet<User> Users => Set<User>();
|
||
public DbSet<HealthRecord> HealthRecords => Set<HealthRecord>();
|
||
// ... 共 15 个
|
||
```
|
||
|
||
2. **在 `OnModelCreating` 中配置表结构**:
|
||
- **JSON 列映射**:HealthRecord.Value 类型是 `JsonDocument`,配置为 PostgreSQL 的 `jsonb` 类型
|
||
- **数组列映射**:`List<string>` 类型的属性(如 MedicalHistory、TimeSlots、Specialty)映射为 PostgreSQL 的 `text[]` 类型
|
||
- **索引配置**:为常查询的列创建数据库索引(加速查询)
|
||
- `Users.Phone` 建唯一索引(手机号不能重复)
|
||
- `Users.Role` 建普通索引(经常按角色查询)
|
||
- `HealthRecords.UserId + Type` 建复合索引("查某用户的血压数据"这种查询)
|
||
- `Consultations.Status` 建索引(查活跃的咨询)
|
||
- 等等...
|
||
- **软删除过滤器**:User 有 `IsDeleted` 标记,配置全局过滤器自动排除已删除的用户
|
||
|
||
---
|
||
|
||
### 5.2 `Data/DataSeeder.cs` — 种子数据
|
||
|
||
**干什么的?** 在数据库首次创建时,自动插入演示数据,方便开发和测试。
|
||
|
||
**插入了什么数据**:
|
||
|
||
| 数据 | 数量 | 说明 |
|
||
|------|------|------|
|
||
| **患者** | 2个 | 张明(男,60岁,高血压+冠心病+PCI术后)、李芳(女,53岁,高血脂+冠心病+PCI术后) |
|
||
| **医生** | 2个 | 王建国(主任医师,心血管内科)、陈雪梅(副主任医师,心血管内科) |
|
||
| **健康记录** | 31天×3条/天 ≈ 93条 | 患者1的血压、心率、体重数据(有随机波动) |
|
||
| **药物** | 4种 | 阿司匹林、替格瑞洛、瑞舒伐他汀、美托洛尔 |
|
||
| **服药记录** | 4药×8天×1~2次/天 ≈ 50条 | 近8天的服药情况,约90%依从率 |
|
||
| **咨询** | 1个 | 张明咨询王建国关于术后复查 |
|
||
| **咨询消息** | 4条 | 一次完整的医患对话 |
|
||
| **通知** | 4条 | 用药提醒、复查提醒、医生回复、健康提示 |
|
||
|
||
**关键技术点**:
|
||
- 所有演示密码都是 `demo123`,使用 SHA256 哈希存储
|
||
- 使用固定随机种子 `new Random(42)`,每次生成的数据一样
|
||
- 时间戳使用 `DateTimeKind.Utc`,因为 PostgreSQL 的 `timestamp with time zone` 要求 UTC 时间
|
||
- 健康数据的 Value 使用 `JsonDocument.Parse()` 创建 JSON 格式
|
||
|
||
---
|
||
|
||
### 5.3 `Services/JwtProvider.cs` — JWT 令牌提供者
|
||
|
||
**干什么的?** 实现了 Domain 层定义的 `IJwtProvider` 接口,生成 JWT 令牌。
|
||
|
||
**两个方法**:
|
||
|
||
1. **`GenerateAccessToken`**:生成访问令牌
|
||
- 从配置文件读取密钥(`appsettings.json` 的 `Jwt:Secret`)
|
||
- 把用户 ID、姓名、角色打包进令牌的"负载"(Claims)
|
||
- 使用 HMAC-SHA256 算法签名
|
||
- 设置 30 分钟过期
|
||
- 返回签好名的 JWT 字符串
|
||
|
||
2. **`GenerateRefreshToken`**:生成刷新令牌
|
||
- 使用加密安全的随机数生成器产生 64 字节随机数据
|
||
- 转成 Base64 字符串返回(就是一个随机字符串,不包含任何信息)
|
||
|
||
**JWT 是怎么工作的?** 可以理解为"盖了章的通行证":
|
||
- 服务器签发令牌时"盖章"(用密钥签名)
|
||
- 客户端每次请求带着令牌
|
||
- 服务器验证"章"是真的 → 放行
|
||
- 如果令牌过期了 → 拒绝,客户端用刷新令牌换新的
|
||
|
||
---
|
||
|
||
## 6. 后端 — WebApi 接口层
|
||
|
||
> 这是最外层,直接接收 HTTP 请求,返回 JSON 响应。
|
||
|
||
### 6.1 `Program.cs` — 应用程序入口
|
||
|
||
**这是整个后端启动的唯一入口文件**。它配置了应用运行所需的一切。
|
||
|
||
**主要做的事(按顺序)**:
|
||
|
||
1. **创建 WebApplication**:初始化 .NET 应用
|
||
2. **注册服务(依赖注入)**:
|
||
- 注册 `AppDbContext`:告诉 EF Core 用 PostgreSQL,连接字符串从配置文件读
|
||
- 注册 JWT 认证:配置验证规则(密钥、签发者、过期时间等)
|
||
- 注册 Swagger:配置 API 文档,支持 JWT 认证测试
|
||
- 注册 SignalR:配置实时通信
|
||
- 注册 CORS:允许前端跨域访问(开发阶段允许所有来源)
|
||
- 注册业务服务:`AuthService`、`HealthService` 等,让控制器能使用
|
||
- 注册 `IJwtProvider` → `JwtProvider`:告诉框架"当需要 IJwtProvider 时,用 JwtProvider"
|
||
3. **配置中间件管道**:
|
||
- 异常处理 → CORS → 认证 → 授权 → 映射控制器 → 映射 SignalR Hub
|
||
4. **初始化数据库**:
|
||
- 确保数据库和表已创建(`EnsureCreatedAsync`)
|
||
- 调用 `DataSeeder.SeedAsync()` 插入演示数据
|
||
5. **启动应用**:开始监听 HTTP 请求
|
||
|
||
---
|
||
|
||
### 6.2 控制器
|
||
|
||
控制器 = 处理 HTTP 请求的"接待员"。每个控制器负责一组相关的 API。
|
||
|
||
---
|
||
|
||
#### `Controllers/AuthController.cs` — 认证接口
|
||
|
||
**路由前缀**:`api/auth`
|
||
|
||
| 端点 | 方法 | 认证 | 做什么 |
|
||
|------|------|------|--------|
|
||
| `POST /api/auth/send-sms` | 公开 | ✗ | 模拟发送短信验证码(演示版直接返回成功) |
|
||
| `POST /api/auth/login` | 公开 | ✗ | 手机号+验证码登录,返回 JWT 令牌对 |
|
||
| `POST /api/auth/register` | 公开 | ✗ | 注册新患者账号 |
|
||
| `POST /api/auth/refresh` | 公开 | ✗ | 用刷新令牌换新的访问令牌对 |
|
||
| `GET /api/auth/me` | 需登录 | ✓ | 获取当前登录用户的完整资料 |
|
||
| `PUT /api/auth/me` | 需登录 | ✓ | 更新当前用户的个人资料 |
|
||
|
||
**细节**:登录接口接收 `{ phone, smsCode }`,验证码固定为 `1234`(演示版)。成功后返回 `{ userId, name, role, accessToken, refreshToken }`。
|
||
|
||
---
|
||
|
||
#### `Controllers/HealthController.cs` — 健康数据接口
|
||
|
||
**路由前缀**:`api/health-records`
|
||
|
||
| 端点 | 方法 | 认证 | 做什么 |
|
||
|------|------|------|--------|
|
||
| `GET /api/health-records` | 需登录 | ✓ | 查询健康记录,可按类型和天数筛选 |
|
||
| `GET /api/health-records/stats` | 需登录 | ✓ | 获取所有类型的统计(最新值、周/月平均、趋势) |
|
||
| `GET /api/health-records/latest/{type}` | 需登录 | ✓ | 获取某类型最新一条记录 |
|
||
| `POST /api/health-records` | 需登录 | ✓ | 添加一条健康记录 |
|
||
|
||
**细节**:查询支持 `?type=blood_pressure&days=30` 参数。医生可以加 `?patientId=xxx` 查看指定患者的数据。
|
||
|
||
---
|
||
|
||
#### `Controllers/MedicationController.cs` — 药物管理接口
|
||
|
||
**路由前缀**:`api/medications`
|
||
|
||
| 端点 | 方法 | 认证 | 做什么 |
|
||
|------|------|------|--------|
|
||
| `GET /api/medications` | 需登录 | ✓ | 列出所有药物 |
|
||
| `POST /api/medications` | 需登录 | ✓ | 添加新药物 |
|
||
| `GET /api/medications/{id}` | 需登录 | ✓ | 获取药物详情 |
|
||
| `GET /api/medications/{id}/records` | 需登录 | ✓ | 获取该药物的所有服药记录 |
|
||
| `POST /api/medications/{id}/take` | 需登录 | ✓ | 标记某时间点已服药 |
|
||
| `GET /api/medications/{id}/adherence` | 需登录 | ✓ | 获取该药物的依从率 |
|
||
|
||
**细节**:`take` 接口接收 `{ timeSlot: "08:00" }`,在相应的时间点记录服药。
|
||
|
||
---
|
||
|
||
#### `Controllers/ConsultationController.cs` — 在线问诊接口
|
||
|
||
**路由前缀**:`api/consultations`
|
||
|
||
| 端点 | 方法 | 认证 | 做什么 |
|
||
|------|------|------|--------|
|
||
| `GET /api/consultations/doctors` | 需登录 | ✓ | 列出可接诊的医生 |
|
||
| `GET /api/consultations` | 需登录 | ✓ | 列出我的咨询(患者看自己的,医生看分配给自己的) |
|
||
| `POST /api/consultations` | 需登录 | ✓ | 发起新咨询 |
|
||
| `GET /api/consultations/{id}/messages` | 需登录 | ✓ | 获取咨询的所有消息 |
|
||
| `POST /api/consultations/{id}/messages` | 需登录 | ✓ | 发送消息 |
|
||
|
||
---
|
||
|
||
#### `Controllers/ReportController.cs` — 报告管理接口
|
||
|
||
**路由前缀**:`api/reports`
|
||
|
||
| 端点 | 方法 | 认证 | 权限 | 做什么 |
|
||
|------|------|------|------|--------|
|
||
| `GET /api/reports` | 需登录 | ✓ | — | 列出报告 |
|
||
| `GET /api/reports/pending` | 需登录 | ✓ | 仅医生 | 列出待审核的报告 |
|
||
| `GET /api/reports/{id}` | 需登录 | ✓ | — | 获取报告详情 |
|
||
| `POST /api/reports` | 需登录 | ✓ | — | 上传报告 |
|
||
| `POST /api/reports/{id}/interpret` | 需登录 | ✓ | 仅医生 | 医生提交解读 |
|
||
|
||
---
|
||
|
||
#### `Controllers/FollowUpController.cs` — 随访管理接口
|
||
|
||
**路由前缀**:`api/follow-ups`
|
||
|
||
| 端点 | 方法 | 认证 | 权限 | 做什么 |
|
||
|------|------|------|------|--------|
|
||
| `GET /api/follow-ups` | 需登录 | ✓ | — | 列出随访计划 |
|
||
| `GET /api/follow-ups/{id}` | 需登录 | ✓ | — | 获取随访详情 |
|
||
| `POST /api/follow-ups` | 需登录 | ✓ | — | 创建随访 |
|
||
| `PUT /api/follow-ups/{id}` | 需登录 | ✓ | 仅医生 | 更新随访 |
|
||
|
||
---
|
||
|
||
#### `Controllers/PatientController.cs` — 患者管理接口(医生专用)
|
||
|
||
**路由前缀**:`api/patients`
|
||
|
||
| 端点 | 方法 | 认证 | 权限 | 做什么 |
|
||
|------|------|------|------|--------|
|
||
| `GET /api/patients` | 需登录 | ✓ | 仅医生 | 列出患者(支持搜索和分页) |
|
||
| `GET /api/patients/{id}` | 需登录 | ✓ | 仅医生 | 获取患者详情 |
|
||
|
||
---
|
||
|
||
#### `Controllers/NotificationController.cs` — 通知接口
|
||
|
||
**路由前缀**:`api/notifications`
|
||
|
||
| 端点 | 方法 | 认证 | 做什么 |
|
||
|------|------|------|--------|
|
||
| `GET /api/notifications` | 需登录 | ✓ | 列出所有通知 |
|
||
| `GET /api/notifications/unread-count` | 需登录 | ✓ | 获取未读数量 |
|
||
| `PUT /api/notifications/{id}/read` | 需登录 | ✓ | 标记单条已读 |
|
||
| `PUT /api/notifications/read-all` | 需登录 | ✓ | 全部标记已读 |
|
||
|
||
---
|
||
|
||
### 6.3 `Hubs/ChatHub.cs` — SignalR 实时聊天枢纽
|
||
|
||
**干什么的?** 实现医生和患者的实时消息推送。
|
||
|
||
**技术原理**:
|
||
- 传统 HTTP:客户端问 → 服务器答(一问一答,服务器不能主动推送)
|
||
- SignalR:建立 WebSocket 长连接 → 服务器可以随时推消息给客户端
|
||
|
||
**主要方法**:
|
||
- `JoinConsultation(consultationId)`:加入一个咨询的"聊天室"
|
||
- `LeaveConsultation(consultationId)`:离开聊天室
|
||
- `SendMessage(consultationId, content)`:发送消息 → 广播给聊天室内的其他人
|
||
|
||
**流程**:
|
||
1. 患者发消息 → `SendMessage` → 保存到数据库 → 广播给医生
|
||
2. 医生收到实时通知 → 可以在聊天页面看到新消息
|
||
|
||
---
|
||
|
||
### 6.4 配置文件
|
||
|
||
#### `appsettings.json` — 主配置文件
|
||
|
||
包含:
|
||
- **PostgreSQL 连接字符串**:`Host=localhost;Port=5432;Database=HealthManager;Username=postgres;Password=postgres123`
|
||
- **JWT 配置**:密钥、签发者、受众、过期时间
|
||
- **Redis 连接字符串**
|
||
- **MinIO 连接字符串**
|
||
|
||
#### `appsettings.Development.json` — 开发环境配置
|
||
|
||
覆盖主配置中的某些值,仅在开发环境生效。
|
||
|
||
#### `Properties/launchSettings.json` — 启动配置
|
||
|
||
定义 Visual Studio / `dotnet run` 启动时的行为:
|
||
- 使用 `http://localhost:5000` 地址
|
||
- 环境设为 Development
|
||
- 自动打开 Swagger 页面
|
||
|
||
---
|
||
|
||
## 7. 病人端前端 (frontend-patient)
|
||
|
||
> 移动端 H5 应用,患者用手机浏览器打开。
|
||
|
||
### 7.1 技术选型
|
||
|
||
| 库 | 版本 | 做什么 |
|
||
|----|------|--------|
|
||
| React | 19 | UI 框架 |
|
||
| TypeScript | 6 | 类型安全 |
|
||
| Vite | 8 | 构建工具 |
|
||
| Zustand | 5 | 状态管理 |
|
||
| React Router | 7 | 路由 |
|
||
| ECharts | 6 | 图表 |
|
||
| Framer Motion | 12 | 动画 |
|
||
| dayjs | 2 | 日期处理 |
|
||
|
||
### 7.2 文件结构说明
|
||
|
||
```
|
||
src/
|
||
├── main.tsx ← React 应用入口,把 App 组件挂到页面上
|
||
├── App.tsx ← 根组件,渲染路由
|
||
├── router/ ← 路由配置
|
||
│ ├── index.tsx ← 定义所有 URL 和页面的映射关系
|
||
│ └── AuthGuard.tsx ← 路由守卫:没登录就跳转到登录页
|
||
├── stores/ ← 状态管理
|
||
│ ├── auth.store.ts ← 认证状态:用户信息、令牌、登录/登出
|
||
│ └── notification.store.ts ← 通知状态:通知列表、未读数
|
||
├── services/ ← API 调用
|
||
│ ├── api-client.ts ← 底层 HTTP 请求(封装 fetch)
|
||
│ ├── auth.service.ts ← 认证 API 调用
|
||
│ ├── health.service.ts ← 健康数据 API 调用
|
||
│ ├── medication.service.ts ← 药物 API 调用
|
||
│ ├── consultation.service.ts ← 咨询 API 调用
|
||
│ ├── report.service.ts ← 报告 API 调用
|
||
│ ├── followup.service.ts ← 随访 API 调用
|
||
│ ├── notification.service.ts ← 通知 API 调用
|
||
│ ├── exercise-diet.service.ts ← 运动/饮食 API 调用
|
||
│ └── device.service.ts ← 设备 API 调用(目前部分用假数据)
|
||
├── types/ ← TypeScript 类型定义
|
||
│ ├── user.ts ← 用户相关类型
|
||
│ ├── health.ts ← 健康数据相关类型
|
||
│ ├── medication.ts ← 药物相关类型
|
||
│ ├── consultation.ts ← 咨询相关类型
|
||
│ ├── report.ts ← 报告相关类型
|
||
│ ├── followup.ts ← 随访相关类型
|
||
│ ├── notification.ts ← 通知相关类型
|
||
│ ├── device.ts ← 设备相关类型
|
||
│ ├── calendar.ts ← 日历相关类型
|
||
│ ├── exercise-diet.ts ← 运动/饮食相关类型
|
||
│ └── index.ts ← 统一导出所有类型
|
||
├── utils/ ← 工具函数
|
||
│ ├── format.ts ← 日期、数字格式化
|
||
│ ├── storage.ts ← localStorage 封装
|
||
│ ├── validator.ts ← 输入验证(手机号、验证码格式)
|
||
│ └── constants.ts ← 常量(测量类型、导航项、健康贴士、运动推荐等)
|
||
├── hooks/ ← 自定义 Hooks
|
||
│ ├── useAuth.ts ← 获取认证状态的便捷 Hook
|
||
│ └── useCountdown.ts ← 短信验证码倒计时
|
||
├── components/ ← 公共组件
|
||
│ ├── common/ ← 通用 UI 组件
|
||
│ │ ├── Button.tsx ← 按钮(主按钮/次按钮/文字按钮,加载中状态)
|
||
│ │ ├── Card.tsx ← 卡片容器
|
||
│ │ ├── Badge.tsx ← 角标(红点/数字)
|
||
│ │ ├── Input.tsx ← 输入框(带标签和错误提示)
|
||
│ │ ├── Toast.tsx ← 轻提示(成功/失败,自动消失)
|
||
│ │ └── Empty.tsx ← 空状态占位
|
||
│ ├── layout/ ← 布局组件
|
||
│ │ ├── AppLayout.tsx ← 主布局:TabBar + 内容区
|
||
│ │ ├── StackLayout.tsx ← 子页面布局:返回按钮 + 内容区(无 TabBar)
|
||
│ │ ├── TabBar.tsx ← 底部导航栏(首页/健康/服务/我的)
|
||
│ │ └── PageHeader.tsx ← 页面标题栏(带返回按钮)
|
||
│ └── charts/ ← 图表组件
|
||
│ ├── LineChart.tsx ← 折线图(展示趋势)
|
||
│ ├── BarChart.tsx ← 柱状图
|
||
│ └── PieChart.tsx ← 饼图(展示依从率等)
|
||
├── pages/ ← 页面组件
|
||
│ ├── auth/ ← 认证页面
|
||
│ │ ├── LoginPage.tsx ← 登录页
|
||
│ │ └── RegisterPage.tsx ← 注册页
|
||
│ ├── home/ ← 首页
|
||
│ │ ├── HomePage.tsx ← 仪表盘首页
|
||
│ │ └── DeviceBindingPage.tsx ← 设备绑定页
|
||
│ ├── health/ ← 健康模块
|
||
│ │ ├── HealthHubPage.tsx ← 健康中心入口
|
||
│ │ ├── HealthRecordListPage.tsx ← 健康记录列表
|
||
│ │ ├── ManualEntryPage.tsx ← 手动输入健康数据
|
||
│ │ ├── TrendChartPage.tsx ← 趋势图
|
||
│ │ └── HealthCalendarPage.tsx ← 健康日历
|
||
│ ├── medication/ ← 用药模块
|
||
│ │ ├── MedicationListPage.tsx ← 药物列表
|
||
│ │ ├── MedicationEditPage.tsx ← 添加/编辑药物
|
||
│ │ └── MedicationDetailPage.tsx ← 药物详情(含依从率饼图)
|
||
│ ├── services/ ← 服务模块
|
||
│ │ ├── ServicesHubPage.tsx ← 服务中心
|
||
│ │ ├── DoctorListPage.tsx ← 医生列表
|
||
│ │ ├── ChatPage.tsx ← 在线问诊聊天
|
||
│ │ ├── ReportListPage.tsx ← 报告列表
|
||
│ │ ├── ReportUploadPage.tsx ← 上传报告
|
||
│ │ ├── ReportDetailPage.tsx ← 报告详情
|
||
│ │ ├── FollowUpListPage.tsx ← 随访列表
|
||
│ │ └── FollowUpEditPage.tsx ← 添加/编辑随访
|
||
│ ├── exercise-diet/ ← 运动饮食模块
|
||
│ │ └── ExerciseDietPage.tsx ← 运动/饮食记录和推荐
|
||
│ ├── profile/ ← 个人中心
|
||
│ │ ├── ProfilePage.tsx ← 个人资料页
|
||
│ │ ├── SettingsPage.tsx ← 设置页
|
||
│ │ └── staticPages.tsx ← 静态页面(通知设置、隐私政策、关于)
|
||
│ └── notifications/ ← 通知
|
||
│ └── NotificationListPage.tsx ← 通知列表
|
||
└── mock/ ← 假数据(开发用,部分已被真实 API 替代)
|
||
├── index.ts ← Mock 工具(延迟模拟、ID 生成)
|
||
├── users.ts ← 假用户数据
|
||
├── health-records.ts ← 假健康数据
|
||
├── medications.ts ← 假药物数据
|
||
├── devices.ts ← 假设备数据
|
||
├── consultations.ts ← 假咨询数据
|
||
├── followups.ts ← 假随访数据
|
||
├── reports.ts ← 假报告数据
|
||
├── notifications.ts ← 假通知数据
|
||
└── exercise-diet.ts ← 假运动饮食数据
|
||
```
|
||
|
||
### 7.3 关键文件详细说明
|
||
|
||
---
|
||
|
||
#### `services/api-client.ts` — HTTP 请求客户端
|
||
|
||
**干什么的?** 所有前端 API 调用的基础。封装了原生的 `fetch` 函数。
|
||
|
||
**核心逻辑**:
|
||
1. 每次请求自动从 localStorage 读取 JWT 令牌,加到请求头的 `Authorization: Bearer xxx`
|
||
2. 发送 HTTP 请求到 `http://localhost:5000`
|
||
3. 如果服务器返回 401(未认证),自动清除本地缓存(令牌过期,需要重新登录)
|
||
4. 把服务器返回的 JSON 统一包装成 `{ code, data, message }` 格式
|
||
5. 如果服务器返回错误,抛出异常
|
||
|
||
**四个方法**:`get`、`post`、`put`、`del`,对应 HTTP 的 GET、POST、PUT、DELETE。
|
||
|
||
---
|
||
|
||
#### `stores/auth.store.ts` — 认证状态管理
|
||
|
||
**干什么的?** 管理"用户是否登录"这个核心状态。
|
||
|
||
**状态数据**:
|
||
- `user`:当前用户信息(姓名、手机号、角色等)
|
||
- `token`:JWT 访问令牌
|
||
- `isAuthenticated`:是否已登录(根据 token 是否存在判断)
|
||
|
||
**操作方法**:
|
||
- `login(phone, code)`:调登录 API → 保存令牌和用户信息
|
||
- `register(phone, code, name)`:调注册 API → 保存令牌和用户信息
|
||
- `logout()`:清除所有数据,回到登录页
|
||
- `updateProfile(data)`:更新用户资料
|
||
|
||
**持久化**:使用 Zustand 的 `persist` 中间件,数据自动存到 localStorage 的 `hrt_auth` 键。即使刷新页面或关闭浏览器,登录状态也不会丢。
|
||
|
||
---
|
||
|
||
#### `router/index.tsx` — 路由配置
|
||
|
||
**干什么的?** 定义了"哪个 URL 显示哪个页面组件"。
|
||
|
||
**路由分为三类**:
|
||
|
||
1. **公开路由**(不需要登录):
|
||
- `/login` → 登录页
|
||
- `/register` → 注册页
|
||
|
||
2. **Tab 路由**(需要登录,底部有导航栏):
|
||
- `/home` → 首页仪表盘
|
||
- `/health` → 健康中心
|
||
- `/services` → 服务中心
|
||
- `/profile` → 个人中心
|
||
|
||
3. **Stack 路由**(需要登录,无底部导航栏,有返回按钮):
|
||
- `/health/records` → 健康记录列表
|
||
- `/health/records/add` → 手动录入
|
||
- `/health/trends/:type` → 趋势图
|
||
- `/services/consultation` → 医生列表
|
||
- `/services/consultation/chat/:doctorId` → 聊天
|
||
- ... 等等
|
||
|
||
**AuthGuard 组件**:包裹在需要登录的路由外面。如果用户没登录访问这些页面,自动跳转到 `/login`。
|
||
|
||
---
|
||
|
||
#### 各页面说明
|
||
|
||
##### 登录页 `pages/auth/LoginPage.tsx`
|
||
- 输入手机号 + 短信验证码
|
||
- 点击"获取验证码"按钮触发 60 秒倒计时
|
||
- 输入框中自动填充演示账号手机号 `13800138000`
|
||
- 验证码任意输入(演示版不验证)
|
||
- 登录成功后跳到首页
|
||
|
||
##### 首页 `pages/home/HomePage.tsx`
|
||
- **健康概览卡片**:显示最新血压和心率数据
|
||
- **快捷操作按钮**(6个):录入健康数据、用药打卡、在线问诊、查看报告、运动饮食、我的随访
|
||
- **健康小贴士**:随机展示一条心血管健康建议
|
||
- **通知角标**:右上角铃铛图标,有未读通知时显示红点
|
||
|
||
##### 健康中心 `pages/health/HealthHubPage.tsx`
|
||
- 6 种测量类型的卡片:血压、心率、血糖、血氧、体重、步数
|
||
- 点击任一卡片进入该类别的记录列表
|
||
- 底部链接:健康日历、用药管理、运动饮食
|
||
|
||
##### 手动录入 `pages/health/ManualEntryPage.tsx`
|
||
- 根据测量类型显示不同的输入表单
|
||
- 血压:收缩压 + 舒张压 + 心率,三个输入框
|
||
- 其他类型:单个数值输入框
|
||
- 日期和时间选择器
|
||
|
||
##### 趋势图 `pages/health/TrendChartPage.tsx`
|
||
- 使用 ECharts 折线图
|
||
- 可选择 7天 / 14天 / 30天 / 90天 范围
|
||
- 血压类型同时显示收缩压和舒张压两条线
|
||
- 收缩压有 140 的红色警戒线
|
||
|
||
##### 药物列表 `pages/medication/MedicationListPage.tsx`
|
||
- 分"服用中"和"已结束"两个标签页
|
||
- 每种药显示名称、剂量、服药时间
|
||
- 右下角浮动按钮,点击添加新药
|
||
|
||
##### 药物详情 `pages/medication/MedicationDetailPage.tsx`
|
||
- 药物完整信息:名称、剂量、频率、时间点、开始日期、注意事项
|
||
- 依从率饼图:绿色=已服用,灰色=未服用
|
||
- 近30天服药日历视图
|
||
|
||
##### 医生列表 `pages/services/DoctorListPage.tsx`
|
||
- 按科室筛选(全部/心血管内科/心外科等)
|
||
- 每个医生卡片:头像、姓名、职称、科室、擅长领域、简介
|
||
- 点击进入聊天页面
|
||
|
||
##### 聊天页 `pages/services/ChatPage.tsx`
|
||
- 对话气泡界面
|
||
- 患者消息靠右(绿色气泡),医生消息靠左(白色气泡)
|
||
- 底部输入框 + 发送按钮
|
||
- 新消息自动滚到底部
|
||
|
||
##### 报告列表 `pages/services/ReportListPage.tsx`
|
||
- 分"全部/待审核/已解读"三个标签
|
||
- 每项显示标题、类别、状态、上传时间
|
||
- 点击查看详情
|
||
|
||
##### 报告详情 `pages/services/ReportDetailPage.tsx`
|
||
- 报告基本信息
|
||
- 如果有解读结果,显示风险等级、总结、各检查项目、医生建议
|
||
|
||
##### 个人中心 `pages/profile/ProfilePage.tsx`
|
||
- 用户头像、姓名、手机号
|
||
- 身体数据展示(身高、体重、病史)
|
||
- 菜单列表:用药管理、我的通知、设备绑定、设置、关于
|
||
- 退出登录按钮
|
||
|
||
---
|
||
|
||
### 7.4 状态管理方案
|
||
|
||
病人端有两个 Zustand Store:
|
||
|
||
| Store | 存储键 | 持久化 | 内容 |
|
||
|-------|--------|--------|------|
|
||
| `auth.store` | `hrt_auth` | ✓ localStorage | user, token, isAuthenticated |
|
||
| `notification.store` | — | ✗ 内存 | notifications[], unreadCount, loading |
|
||
|
||
**为什么通知不持久化?** 通知数据变化频繁,每次打开应用重新从服务器获取最新的即可。
|
||
|
||
---
|
||
|
||
## 8. 医生端前端 (frontend-doctor)
|
||
|
||
> PC 端 Web 应用,医生在电脑浏览器中使用。
|
||
|
||
### 8.1 文件结构
|
||
|
||
```
|
||
src/
|
||
├── main.tsx ← 应用入口
|
||
├── App.tsx ← 根组件
|
||
├── router/
|
||
│ ├── index.tsx ← 路由配置(定义 URL 和页面映射)
|
||
│ └── AuthGuard.tsx ← 路由守卫
|
||
├── stores/
|
||
│ └── auth.store.ts ← 认证状态(唯一的状态管理)
|
||
├── services/
|
||
│ └── api-client.ts ← HTTP 客户端(封装 fetch)
|
||
├── types/
|
||
│ └── index.ts ← 所有类型定义
|
||
├── components/
|
||
│ └── layout/
|
||
│ └── DoctorLayout.tsx ← 主布局:侧边栏 + 内容区
|
||
└── pages/
|
||
├── auth/
|
||
│ └── LoginPage.tsx ← 登录页
|
||
├── dashboard/
|
||
│ └── DashboardPage.tsx ← 工作台首页
|
||
├── patients/
|
||
│ ├── PatientListPage.tsx ← 患者列表
|
||
│ └── PatientDetailPage.tsx ← 患者详情
|
||
├── consultations/
|
||
│ ├── ConsultationListPage.tsx ← 咨询列表
|
||
│ └── ChatPage.tsx ← 聊天页
|
||
├── reports/
|
||
│ ├── ReportListPage.tsx ← 报告列表
|
||
│ └── ReportDetailPage.tsx ← 报告解读
|
||
├── followups/
|
||
│ ├── FollowUpListPage.tsx ← 随访列表
|
||
│ └── FollowUpEditPage.tsx ← 创建/编辑随访
|
||
└── settings/
|
||
└── ProfilePage.tsx ← 个人资料设置
|
||
```
|
||
|
||
### 8.2 关键文件详细说明
|
||
|
||
---
|
||
|
||
#### `components/layout/DoctorLayout.tsx` — 主布局
|
||
|
||
**干什么的?** 医生端所有页面的"壳"。左边是固定的侧边栏,右边是页面内容。
|
||
|
||
**侧边栏内容**:
|
||
- **Logo/标题**:"健康管理平台"
|
||
- **导航菜单**(5项):
|
||
- 📊 工作台 → `/dashboard`
|
||
- 👥 患者管理 → `/patients`
|
||
- 💬 在线问诊 → `/consultations`
|
||
- 📋 报告审核 → `/reports`
|
||
- 📅 随访管理 → `/follow-ups`
|
||
- **底部个人信息**:医生名字、科室、职称
|
||
- **退出登录按钮**
|
||
|
||
**技术细节**:左侧侧边栏宽度 220px,深色背景(`#1a1a2e`)。右侧内容区用 React Router 的 `<Outlet />` 渲染子路由页面。使用 `NavLink` 组件高亮当前活跃的菜单项。
|
||
|
||
---
|
||
|
||
#### `stores/auth.store.ts` — 认证状态
|
||
|
||
**和病人端的区别**:
|
||
- 存储键是 `doc_auth`(不是 `hrt_auth`)
|
||
- 登录后**验证角色必须是 `doctor`**,如果患者尝试登录医生端会报错"仅限医生账号登录"
|
||
- 登录成功后额外调一次 `GET /api/auth/me` 获取医生完整资料(科室、职称等)
|
||
|
||
---
|
||
|
||
#### `router/index.tsx` — 路由配置
|
||
|
||
| 路由 | 页面 | 说明 |
|
||
|------|------|------|
|
||
| `/login` | LoginPage | 登录(公开,无需认证) |
|
||
| `/` | → 重定向到 `/dashboard` | — |
|
||
| `/dashboard` | DashboardPage | 工作台首页 |
|
||
| `/patients` | PatientListPage | 患者列表 |
|
||
| `/patients/:id` | PatientDetailPage | 患者详情 + 健康数据 |
|
||
| `/consultations` | ConsultationListPage | 咨询列表 |
|
||
| `/consultations/:id` | ChatPage | 聊天页 |
|
||
| `/reports` | ReportListPage | 报告列表 |
|
||
| `/reports/:id` | ReportDetailPage | 报告详情 + 解读 |
|
||
| `/follow-ups` | FollowUpListPage | 随访列表 |
|
||
| `/follow-ups/:id/edit` | FollowUpEditPage | 新建/编辑随访 |
|
||
| `/profile` | ProfilePage | 个人设置 |
|
||
|
||
所有路由(除 `/login`)都包裹在 `<AuthGuard>` + `<DoctorLayout>` 中,确保必须登录才能访问。
|
||
|
||
---
|
||
|
||
#### 各页面详细说明
|
||
|
||
##### 登录页 `pages/auth/LoginPage.tsx`
|
||
- 自动填充演示医生账号手机号 `13700137000`
|
||
- 输入验证码(任意输入即可,演示版不验证)
|
||
- 成功后跳到工作台
|
||
|
||
##### 工作台 `pages/dashboard/DashboardPage.tsx`
|
||
- 顶部:问候语 + 医生姓名
|
||
- **4 个统计卡片**(一排显示):
|
||
- 患者总数
|
||
- 活跃咨询数
|
||
- 待审核报告数
|
||
- 今日随访数
|
||
- **快捷操作区域**:查看患者、查看咨询、审核报告、管理随访
|
||
- **今日任务列表**:列出当天的随访安排
|
||
|
||
##### 患者列表 `pages/patients/PatientListPage.tsx`
|
||
- 表格展示:姓名、手机号、性别、病史、支架日期
|
||
- 顶部搜索框,支持按姓名或手机号过滤
|
||
- 点击"查看详情"进入患者详情页
|
||
|
||
##### 患者详情 `pages/patients/PatientDetailPage.tsx`
|
||
- **基本信息区**:姓名、手机号、性别、出生日期、身高体重
|
||
- **病史区**:疾病列表、支架日期、支架类型
|
||
- **健康数据区**:最近30天的血压、心率、血糖等数据卡片,显示最新值和趋势
|
||
|
||
##### 咨询列表 `pages/consultations/ConsultationListPage.tsx`
|
||
- 显示分配给当前医生的所有咨询
|
||
- 每条:患者姓名、咨询主题、状态标签(进行中/已结束)、开始时间
|
||
- 点击进入聊天页
|
||
|
||
##### 聊天页 `pages/consultations/ChatPage.tsx`
|
||
- 和病人端类似的对话界面
|
||
- 医生消息靠右(蓝色气泡),患者消息靠左(白色气泡)
|
||
- 输入框 + 发送按钮,也支持 Enter 键发送
|
||
- 发送后自动滚到底部
|
||
|
||
##### 报告列表 `pages/reports/ReportListPage.tsx`
|
||
- 表格展示:患者姓名、报告标题、类别、状态(待审核/已完成)、上传日期、操作
|
||
- 点击"查看"进入报告详情
|
||
|
||
##### 报告详情 + 解读 `pages/reports/ReportDetailPage.tsx`
|
||
- 上半部分:报告的基本信息(标题、类别、状态、上传时间)
|
||
- 如果有上传的图片链接,显示链接
|
||
- 下半部分:**解读表单**
|
||
- 解读结果文本框
|
||
- 提交后报告状态变为"已完成"
|
||
|
||
##### 随访列表 `pages/followups/FollowUpListPage.tsx`
|
||
- 每条随访显示:标题、患者姓名、计划时间、状态(待随访/已完成/已错过)
|
||
- 右上角"+ 新建随访"按钮
|
||
- 每条旁边有"编辑"按钮
|
||
|
||
##### 新建/编辑随访 `pages/followups/FollowUpEditPage.tsx`
|
||
- 如果路由参数 `:id` 为 `"new"`:创建新随访(POST)
|
||
- 否则:编辑已有随访(PUT),先预填现有数据
|
||
- 表单字段:标题、患者(下拉选择)、计划时间、备注
|
||
- 保存后跳转回随访列表
|
||
|
||
##### 个人设置 `pages/settings/ProfilePage.tsx`
|
||
- 预填当前医生的姓名、手机号(不可修改)、科室、职称、简介
|
||
- 修改后保存,同时更新本地缓存
|
||
|
||
---
|
||
|
||
### 8.3 状态管理
|
||
|
||
医生端只有一个 Zustand Store(`auth.store`),比病人端更简单。因为医生端的核心需求是"管理数据",不像患者端有通知等高频变化的状态。
|
||
|
||
### 8.4 样式方案
|
||
|
||
医生端使用**内联样式(inline style)**,每个组件直接在 JSX 中写 `style={{...}}`。这样做的好处是:
|
||
- 样式和组件紧密绑定,不会出现样式冲突
|
||
- 不需要额外的 CSS 文件
|
||
- 适合组件数量不多的项目
|
||
|
||
全局基础样式在 `src/index.css` 中定义(CSS 变量、字体、基础布局)。
|
||
|
||
---
|
||
|
||
## 9. 数据库表结构
|
||
|
||
### 9.1 所有表一览
|
||
|
||
| 表名 | 用途 | 主要查询场景 |
|
||
|------|------|-------------|
|
||
| `Users` | 用户(患者+医生+管理员) | 按手机号查、按角色查、按姓名搜索 |
|
||
| `RefreshTokens` | JWT 刷新令牌 | 按令牌字符串查、按用户查 |
|
||
| `Devices` | 智能设备 | 按用户查绑定的设备 |
|
||
| `HealthRecords` | 健康测量数据 | 按用户+类型查、按时间范围查、取最新值 |
|
||
| `DietRecords` | 饮食记录 | 按用户+日期查 |
|
||
| `ExerciseRecords` | 运动记录 | 按用户+日期查 |
|
||
| `Medications` | 药物方案 | 按用户查、按状态查、按医生查 |
|
||
| `MedicationRecords` | 服药记录 | 按药物查、按用户+时间查 |
|
||
| `Consultations` | 咨询会话 | 按患者查、按医生查、按状态查 |
|
||
| `ConsultationMessages` | 咨询消息 | 按咨询查、按发送者查 |
|
||
| `QuickReplyTemplates` | 快捷回复模板 | 按医生查 |
|
||
| `Reports` | 检查报告 | 按患者查、按医生查、按状态查 |
|
||
| `ReportItems` | 报告检查项 | 按报告查 |
|
||
| `FollowUps` | 随访计划 | 按患者查、按医生查、按计划时间查 |
|
||
| `Notifications` | 通知 | 按用户查、按已读/未读查 |
|
||
|
||
### 9.2 重要索引
|
||
|
||
数据库索引就像书的目录,能大幅加速查询。
|
||
|
||
| 表 | 索引 | 加速的查询 |
|
||
|----|------|-----------|
|
||
| `Users` | Phone (唯一索引) | 登录时按手机号查找用户 |
|
||
| `Users` | Role | 列出所有医生/患者 |
|
||
| `HealthRecords` | UserId + Type | 查某人的血压数据 |
|
||
| `HealthRecords` | RecordedAt | 按时间排序/筛选 |
|
||
| `Medications` | UserId | 查某人的药物 |
|
||
| `Medications` | Status | 查活跃的药物 |
|
||
| `MedicationRecords` | UserId + CreatedAt | 查最近的服药记录 |
|
||
| `Consultations` | Status | 查活跃的咨询 |
|
||
| `Consultations` | PatientId, DoctorId | 查某人参与的咨询 |
|
||
| `Reports` | Status | 查待审核的报告 |
|
||
| `FollowUps` | ScheduledAt | 按预约时间排序 |
|
||
| `Notifications` | UserId + IsRead | 查某人的未读通知 |
|
||
| `RefreshTokens` | Token (唯一) | 刷新令牌时查找 |
|
||
|
||
### 9.3 特殊数据类型
|
||
|
||
PostgreSQL 有一些 MySQL 没有的特殊类型,本项目充分利用了它们:
|
||
|
||
| PostgreSQL 类型 | 对应 C# 类型 | 使用场景 |
|
||
|-----------------|-------------|---------|
|
||
| `uuid` | `Guid` | 所有主键 |
|
||
| `jsonb` | `JsonDocument` | HealthRecord.Value(灵活存储不同格式的测量数据) |
|
||
| `text[]` | `List<string>` | MedicalHistory、TimeSlots、Specialty、ImageUrls |
|
||
| `timestamp with time zone` | `DateTime` (UTC) | 所有时间戳字段 |
|
||
| `date` | `DateOnly` | 出生日期、开始日期、记录日期 |
|
||
|
||
---
|
||
|
||
## 10. 常见问题 FAQ
|
||
|
||
### Q1: 为什么所有 ID 都用 Guid 而不是自增数字?
|
||
|
||
- **安全**:自增 ID 会暴露数据量(用户可以猜 ID 访问别人的数据)
|
||
- **分布式友好**:如果将来要多台服务器,Guid 不会冲突
|
||
- **前端可以预生成**:不需要等数据库返回 ID
|
||
|
||
### Q2: 为什么患者和医生放在同一张 Users 表?
|
||
|
||
- 简化设计:登录逻辑完全一样
|
||
- 通过 `Role` 字段区分权限
|
||
- 用"不用的字段留空"的方式处理差异(患者字段对医生为空,反之亦然)
|
||
|
||
### Q3: JWT 令牌过期了怎么办?
|
||
|
||
Access Token 有效期只有 30 分钟。过期后,前端自动用 Refresh Token 去换新的 Access Token。如果 Refresh Token 也过期了(7天后),用户需要重新登录。
|
||
|
||
### Q4: 健康数据的 Value 为什么用 JSON?
|
||
|
||
不同测量类型数据结构不同:
|
||
- 血压:`{systolic: 120, diastolic: 80, pulse: 72}`
|
||
- 心率:`{value: 72}`
|
||
- 步数:`{value: 8500}`
|
||
|
||
如果每种类型建一张表,维护成本高。JSON 列提供灵活性,同时 PostgreSQL 的 jsonb 类型支持索引和查询。
|
||
|
||
### Q5: 前端怎么和后端通信?
|
||
|
||
前端通过 HTTP 请求和后端通信,具体流程:
|
||
1. 前端 `api-client.ts` 封装了 `fetch` 函数
|
||
2. 每次请求自动带上 JWT 令牌(在请求头 `Authorization: Bearer xxx`)
|
||
3. 后端 `[Authorize]` 属性检查令牌是否有效
|
||
4. 如果令牌过期(401),前端自动清除登录状态
|
||
|
||
### Q6: 如何启动整个项目?
|
||
|
||
1. **启动基础设施**:运行 `D:\APP\start-dev.bat`
|
||
- 启动 PostgreSQL、Redis、MinIO
|
||
- 启动后端 API(端口 5000)
|
||
2. **启动病人前端**:`cd frontend-patient && npm run dev`(端口 5173)
|
||
3. **启动医生前端**:`cd frontend-doctor && npm run dev`(端口 5174)
|
||
|
||
### Q7: 数据库在哪里?
|
||
|
||
PostgreSQL 数据文件在 `D:\APP\data\pgdata\`。数据库名 `HealthManager`,用户名 `postgres`,密码 `postgres123`。
|
||
|
||
### Q8: 如果数据库出问题了怎么重置?
|
||
|
||
1. 停止后端
|
||
2. 连接到 PostgreSQL 删除数据库:`DROP DATABASE "HealthManager";`
|
||
3. 重启后端,EF Core 会自动重建数据库和表,DataSeeder 会重新插入演示数据
|
||
|
||
---
|
||
|
||
> 文档版本:v1.0
|
||
> 最后更新:2026-05-20
|
||
> 适用项目:HealthManager 健康管理平台
|