diff --git a/backend/技术文档-后端.md b/backend/技术文档-后端.md new file mode 100644 index 0000000..fd0f1e5 --- /dev/null +++ b/backend/技术文档-后端.md @@ -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\ | 服药时间点,如 ["08:00", "20:00"],存为 PostgreSQL text[] | +| Status | string | active / completed / stopped | + +#### Report(报告) + +| 属性 | 类型 | 说明 | +|------|------|------| +| Category | string | 血液检查 / 心电图 / 影像学 / 尿液检查 | +| ImageUrls | List\ | 报告图片地址列表 | +| 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` → PostgreSQL `text[]` 类型 + - 索引:Phone(唯一)、Role、UserId+Type、Status、ScheduledAt 等 + - 外键关系:用户→健康记录、用户→药物、咨询→消息 等 + +### 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. **注册业务服务**:AuthService、HealthService 等 +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` | 启动配置:端口 5000,Development 环境,自动开 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` | MedicalHistory、TimeSlots、ImageUrls | +| `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 Token。Refresh 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 | 后端技术文档 diff --git a/frontend-doctor/技术文档-医生前端.md b/frontend-doctor/技术文档-医生前端.md new file mode 100644 index 0000000..3c5132d --- /dev/null +++ b/frontend-doctor/技术文档-医生前端.md @@ -0,0 +1,232 @@ +# HealthManager 医生前端 — 技术文档 + +> PC 端 Web 应用,医生在电脑浏览器中使用。 + +--- + +## 目录 + +1. [文件结构](#1-文件结构) +2. [关键文件详解](#2-关键文件详解) +3. [页面详解](#3-页面详解) +4. [状态管理](#4-状态管理) +5. [样式方案](#5-样式方案) +6. [常见问题 FAQ](#6-常见问题-faq) + +--- + +## 1. 文件结构 + +``` +src/ +├── main.tsx ← 应用入口 +├── App.tsx ← 根组件 +├── router/ +│ ├── index.tsx ← URL 和页面映射 +│ └── AuthGuard.tsx ← 路由守卫 +├── stores/ +│ └── auth.store.ts ← 认证状态(唯一的状态管理) +├── services/ +│ └── api-client.ts ← HTTP 客户端 +├── 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 ← 个人资料 +``` + +--- + +## 2. 关键文件详解 + +### 2.1 `components/layout/DoctorLayout.tsx` — 主布局 + +医生端所有页面的"壳"。左侧深蓝色侧边栏(`#0F1D3D`,220px 宽),右侧白色内容区。 + +**侧边栏内容**: +- Logo:"♥ 健康管家" + "医生工作台" +- **导航菜单**(5 项):工作台、患者管理、在线问诊、报告审核、随访管理 +- 当前菜单项蓝底高亮,其他灰色 +- 底部:医生姓名 + 科室职称 + 退出按钮 + +使用 React Router 的 `` + `` 实现。 + +### 2.2 `stores/auth.store.ts` — 认证状态 + +**和患者端的区别**: +- 存储键 `doc_auth`(患者端是 `hrt_auth`) +- 登录后验证 `role === 'doctor'`,否则报错"仅限医生账号登录" +- 登录成功后额外调 `GET /api/auth/me` 获取医生完整资料 +- 使用 `persist` 中间件持久化到 localStorage + +**接口**: +```ts +interface AuthState { + user: User | null; + token: string | null; + isAuthenticated: boolean; + login(phone, code): Promise; + logout(): void; + updateProfile(data): void; +} +``` + +### 2.3 `services/api-client.ts` — HTTP 客户端 + +与患者端逻辑相同: +- 公开接口(login 等)不附加令牌 +- 其他请求自动带 `Authorization: Bearer xxx` +- 401 → 清除 `doc_auth` + 跳转 `/login` +- 统一包装响应为 `{ code, data, message }` + +--- + +## 3. 页面详解 + +### 登录页 `LoginPage.tsx` + +- 手机号 + 验证码输入 +- 演示医生账号:`13700137000`,验证码随便填 +- 成功后跳工作台 + +### 工作台 `DashboardPage.tsx` + +- 顶部:"欢迎回来,王建国" +- **4 个统计卡片**:患者总数(蓝)、进行中问诊(绿)、待审核报告(橙)、今日随访(紫) +- **快捷操作**:患者列表、在线问诊、报告审核、随访管理(用 `` 跳转) +- **今日待办**:列出待审核报告数、进行中问诊数、今日随访数 + +### 患者管理 + +**患者列表 `PatientListPage.tsx`**: +- 搜索框,支持按姓名/手机号过滤 +- 表格:姓名、手机号、性别、支架日期、支架类型 +- 点击进入详情 + +**患者详情 `PatientDetailPage.tsx`**: +- 调 `GET /api/patients/{id}` 获取基本信息 +- 调 `GET /api/health-records?patientId={id}&days=30` 获取健康数据 +- 显示:姓名、手机、性别、身高体重、病史、最近健康数据卡片 + +### 在线问诊 + +**咨询列表 `ConsultationListPage.tsx`**: +- 所有分配给当前医生的咨询 +- 每条:患者姓名、主题、状态标签(进行中/已结束)、日期 +- 点击进入聊天 + +**聊天页 `ChatPage.tsx`**: +- 对话气泡界面 +- 医生消息靠右蓝色,患者消息靠左白色 +- 输入框 + 发送按钮(Enter 发送) + +### 报告审核 + +**报告列表 `ReportListPage.tsx`**: +- 表格:患者、标题、分类、状态、日期、操作 +- 状态:待审核(橙)/ 已完成(绿) +- 点"查看"进详情 + +**报告详情 `ReportDetailPage.tsx`**: +- 报告基本信息 + 图片列表 +- **如果未解读**:文本框 + 提交按钮 → 调 `POST /reports/{id}/interpret` +- **如果已解读**:显示解读结果(总结、风险等级、各检查项、建议) + +### 随访管理 + +**随访列表 `FollowUpListPage.tsx`**: +- 每条:标题、患者、日期、状态(待随访/已完成/已错过) +- "+ 新建随访"按钮 → 跳编辑页 +- "编辑"链接 → 跳编辑页 + +**随访编辑 `FollowUpEditPage.tsx`**: +- 新建:空白表单,POST 创建 +- 编辑:预填现有数据,PUT 更新 +- 字段:标题、患者选择、计划时间、备注 + +### 个人设置 `ProfilePage.tsx` + +- 预填姓名、手机号(不可改)、科室、职称、简介 +- 修改后调 `PUT /api/auth/me` 保存 + +--- + +## 4. 状态管理 + +医生端只有一个 Zustand Store(`auth.store`),比患者端更简单。因为医生端核心需求是"管理数据",不像患者端有通知等高频变化的状态。 + +| Store | 存储键 | 持久化 | 内容 | +|-------|--------|--------|------| +| `auth.store` | `doc_auth` | ✓ localStorage | user, token, isAuthenticated | + +--- + +## 5. 样式方案 + +医生端使用**内联样式(inline style)**,每个组件直接在 JSX 中写 `style={{...}}`。 + +**优点**: +- 样式和组件紧密绑定,无冲突 +- 不需要额外 CSS 文件 +- 适合组件数量不多的项目 + +**全局基础**在 `src/index.css` 中(CSS 变量、字体、基础布局)。 + +--- + +## 6. 常见问题 FAQ + +### Q1: 医生端怎么启动? + +```bash +cd frontend-doctor +npm run dev +# 浏览器打开 http://localhost:5174 +``` + +### Q2: 演示医生账号是什么? + +手机号 `13700137000`(王建国),验证码随便填。 + +### Q3: 和患者端有什么区别? + +| 对比 | 患者端 | 医生端 | +|------|--------|--------| +| 端口 | 5173 | 5174 | +| 存储键 | `hrt_auth` | `doc_auth` | +| 布局 | 底部 TabBar | 左侧 Sidebar | +| 目标设备 | 手机 H5 | PC 浏览器 | +| 样式 | CSS Modules | Inline Style | + +### Q4: 为什么医生看报告列表是空的? + +报告列表调 `GET /api/reports`,医生角色会返回全部患者报告。如果为空,检查: +- 患者端是否上传了报告 +- 后端是否运行(`ERR_CONNECTION_REFUSED` 则是后端没启动) + +### Q5: 状态栏是怎么高亮的? + +使用 React Router 的 `` 组件,`style` 回调中的 `isActive` 参数判断当前 URL 是否匹配。 + +--- + +> 文档版本:v1.0 | 最后更新:2026-05-20 | 医生前端技术文档 diff --git a/frontend-patient/技术文档-患者前端.md b/frontend-patient/技术文档-患者前端.md new file mode 100644 index 0000000..5a570a1 --- /dev/null +++ b/frontend-patient/技术文档-患者前端.md @@ -0,0 +1,298 @@ +# HealthManager 患者前端 — 技术文档 + +> 移动端 H5 应用,患者用手机浏览器打开。 + +--- + +## 目录 + +1. [技术选型](#1-技术选型) +2. [文件结构](#2-文件结构) +3. [关键文件详解](#3-关键文件详解) +4. [页面详解](#4-页面详解) +5. [状态管理](#5-状态管理) +6. [API 调用方式](#6-api-调用方式) +7. [常见问题 FAQ](#7-常见问题-faq) + +--- + +## 1. 技术选型 + +| 库 | 版本 | 做什么 | +|----|------|--------| +| React | 19 | UI 框架 | +| TypeScript | 6 | 类型安全 | +| Vite | 8 | 构建工具 | +| Zustand | 5 | 状态管理 | +| React Router | 7 | 路由(页面跳转) | +| ECharts | 6 | 图表(趋势图、饼图) | +| Framer Motion | 12 | 动画 | +| dayjs | 2 | 日期处理 | + +--- + +## 2. 文件结构 + +``` +src/ +├── main.tsx ← React 应用入口 +├── App.tsx ← 根组件,渲染路由 +├── router/ +│ ├── index.tsx ← URL 和页面的映射关系 +│ └── AuthGuard.tsx ← 路由守卫:没登录→跳登录页 +├── stores/ +│ ├── auth.store.ts ← 认证状态:用户、令牌、登录/登出 +│ └── notification.store.ts ← 通知状态 +├── services/ +│ ├── 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/ +│ ├── 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 ← 日期/数字格式化 +│ ├── validator.ts ← 手机号/验证码格式校验 +│ └── constants.ts ← 常量(测量类型、健康贴士等) +├── hooks/ +│ ├── useAuth.ts ← 获取认证状态 +│ └── useCountdown.ts ← 验证码倒计时 +├── components/ +│ ├── common/ +│ │ ├── Button.tsx ← 按钮(支持 loading 状态) +│ │ ├── Card.tsx ← 卡片容器 +│ │ ├── Badge.tsx ← 角标(红点/数字) +│ │ ├── Input.tsx ← 输入框(带标签+错误提示) +│ │ ├── Toast.tsx ← 轻提示 +│ │ └── Empty.tsx ← 空状态占位 +│ ├── layout/ +│ │ ├── AppLayout.tsx ← 主布局:TabBar + 内容区 +│ │ ├── StackLayout.tsx ← 子页面布局:返回按钮 + 内容区 +│ │ ├── TabBar.tsx ← 底部导航栏(首页/健康/服务/我的) +│ │ └── PageHeader.tsx ← 页面标题栏 +│ └── charts/ +│ ├── LineChart.tsx ← ECharts 折线图 +│ ├── BarChart.tsx ← ECharts 柱状图 +│ └── PieChart.tsx ← ECharts 饼图 +└── pages/ + ├── auth/ + │ ├── LoginPage.tsx ← 登录页 + │ └── RegisterPage.tsx ← 注册页 + ├── home/ + │ ├── HomePage.tsx ← 仪表盘首页 + │ └── DeviceBindingPage.tsx ← 设备绑定 + ├── health/ ← 健康模块 + ├── medication/ ← 用药模块 + ├── services/ ← 服务模块(问诊/报告/随访) + ├── exercise-diet/ ← 运动饮食 + ├── profile/ ← 个人中心 + └── notifications/ ← 通知 +``` + +--- + +## 3. 关键文件详解 + +### 3.1 `services/api-client.ts` — HTTP 请求客户端 + +**所有前端 API 调用的基础**,封装了原生 `fetch`。 + +**核心逻辑**: +1. 公开接口(login/register/send-sms/refresh)**不附加** Authorization 头 +2. 其他请求自动从 localStorage 读取 JWT 令牌,加到 `Authorization: Bearer xxx` +3. 发送 HTTP 请求到 `http://localhost:5000` +4. 如果返回 401(未认证)→ 清除 localStorage,跳转登录页 +5. 把服务器返回的 JSON 统一包装成 `{ code, data, message }` + +**四个方法**:`get`、`post`、`put`、`del` + +### 3.2 `services/auth.service.ts` — 认证服务 + +| 函数 | 做的事 | +|------|--------| +| `login(phone, smsCode)` | 调 `POST /api/auth/login` → 返回 `{token, user}` | +| `register(phone, smsCode, name)` | 调 `POST /api/auth/register` | +| `getProfile()` | 调 `GET /api/auth/me` → 更新 localStorage 中的用户资料 | +| `updateProfile(data)` | 调 `PUT /api/auth/me` → 更新姓名/性别/身高/体重/病史 | +| `logout()` | 清除 localStorage 的 `hrt_auth` | + +### 3.3 `services/health.service.ts` — 健康数据服务 + +| 函数 | API | 说明 | +|------|-----|------| +| `getRecords({type, startDate, endDate})` | `GET /health-records` | 查记录列表 | +| `addRecord(record)` | `POST /health-records` | 添加记录。自动把 value 转成 JSON 字符串 | +| `getLatestStats()` | `GET /health-records?days=7` | 拉取 7 天数据,客户端计算各类型的均值和趋势 | +| `getTrendData(type, days)` | `GET /health-records?type=&days=` | 趋势图数据 | + +**Value 解析逻辑**:`parseValue()` 根据 type 解析 JSON: +- `blood_pressure` → `{systolic, diastolic}` 对象 +- 其他类型 → `{value}` 中的数字 + +### 3.4 `services/medication.service.ts` — 用药服务 + +| 函数 | API | 说明 | +|------|-----|------| +| `getMedications()` | `GET /medications` | 药物列表 | +| `addMedication(data)` | `POST /medications` | 添加药物 | +| `markTaken(medicationId, slot)` | `POST /medications/{id}/take` | 打卡 | +| `getAdherence(medicationId)` | `GET /medications/{id}/adherence` | 依从率 | + +### 3.5 `services/consultation.service.ts` — 咨询 + +| 函数 | API | 说明 | +|------|-----|------| +| `getDoctors()` | `GET /consultations/doctors` | 可接诊医生列表 | +| `getConsultation(doctorId)` | `GET /consultations` | 查与某医生的活跃咨询 | +| `startConsultation(doctorId)` | `POST /consultations` | 发起新咨询 | +| `sendMessage(consultationId, text)` | `POST /consultations/{id}/messages` | 发送消息 | +| `getDoctorReply(consultationId)` | `GET /consultations/{id}/messages` | 拉取最新消息,找医生的回复 | + +### 3.6 `stores/auth.store.ts` — 认证状态 + +**状态数据**:`user`, `token`, `isAuthenticated` + +**操作方法**: +- `login(phone, code)` → 调 API → 保存 token → 再拉完整用户资料 +- `register(phone, code, name)` → 同上 +- `logout()` → 清除所有数据 +- `updateProfile(data)` → 只更新内存中的用户对象 + +**持久化**:Zustand `persist` 中间件 → `localStorage` 的 `hrt_auth` 键。刷新页面不丢登录状态。 + +### 3.7 `router/index.tsx` — 路由 + +分为三类: + +1. **公开路由**(无需登录):`/login`、`/register` +2. **Tab 路由**(需登录,底部导航栏):`/home`、`/health`、`/services`、`/profile` +3. **Stack 路由**(需登录,有返回按钮):所有子页面如 `/health/records/add`、`/services/consultation/chat/:doctorId` 等 + +`AuthGuard` 组件包裹在需要登录的路由外,未登录自动跳 `/login`。 + +--- + +## 4. 页面详解 + +### 登录页 `LoginPage.tsx` + +- 输入手机号 + 短信验证码 +- 点"获取验证码" → 60 秒倒计时 +- 验证码任意输入(演示版) +- 成功后跳首页 + +### 首页 `HomePage.tsx` + +- **健康概览卡片**(蓝色渐变):最新血压 + 心率,异常值变色 +- **快捷操作**(6 个):测血压、记用药、在线问诊、报告解读、健康日历、运动饮食 +- **健康小贴士**:随机展示,点击换下一条 +- **通知角标**:未读通知红点 + +### 健康中心 `HealthHubPage.tsx` + +6 种测量类型卡片 → 点击进记录列表 → 点"+ 录入" → 填数值 → 保存 + +### 趋势图 `TrendChartPage.tsx` + +- ECharts 折线图,可选 7/14/30/90 天 +- 血压同时显示收缩压+舒张压两条线,140 警戒线 + +### 药物管理 + +- **列表**:分"进行中/已结束"Tab +- **添加**:选常用药或手动输入,设剂量、频次(每日1~3次)、服药时间点 +- **详情**:依从率饼图,近30天服药记录 + +### 在线问诊 + +- **医生列表**:按科室筛选 → 点医生 → 进聊天 +- **聊天**:对话气泡,自动滚底 +- 发送后 1.5 秒模拟医生回复(演示版) + +### 报告管理 + +- **上传**:填标题 → 选类别 → 点虚线框选图片 → 提交 +- **列表**:分状态 Tab 查看 +- **详情**:查看解读结果 + +### 个人中心 `ProfilePage.tsx` + +- 用户信息卡片(点击进入编辑) +- 身体数据展示 +- 菜单:用药、通知、设备、设置、关于 +- 退出登录 + +### 编辑资料 `EditProfilePage.tsx` + +可修改:姓名、性别、生日、身高(cm)、体重(kg)、病史(顿号分隔) + +--- + +## 5. 状态管理 + +| Store | 存储键 | 持久化 | 内容 | +|-------|--------|--------|------| +| `auth.store` | `hrt_auth` | ✓ localStorage | user, token, isAuthenticated | +| `notification.store` | — | ✗ 内存 | notifications[], unreadCount | + +通知不持久化——每次打开应用重新从服务器拉取。 + +--- + +## 6. API 调用方式 + +``` +前端 services → api-client.ts → fetch() → http://localhost:5000 → 后端 Controller +``` + +- 所有 API 请求经过 `api-client.ts` 统一处理 +- 自动带 JWT 令牌(公开接口除外) +- 401 自动清除登录态并跳转登录页 +- 响应统一为 `{ code, data, message }` 格式 + +--- + +## 7. 常见问题 FAQ + +### Q1: 前端怎么调后端 API? + +前端 `services/` 下的每个 service 文件封装了一类 API 调用。底层都通过 `api-client.ts` 发 HTTP 请求到 `http://localhost:5000`。 + +### Q2: 怎么测这个前端? + +```bash +cd frontend-patient +npm run dev +# 浏览器打开 http://localhost:5173 +# F12 → 左上角 📱 图标 → 选 iPhone 12 Pro 模拟手机 +``` + +### Q3: 演示账号是什么? + +手机号 `13800138000`,验证码随便填(1234 就行)。 + +### Q4: 为什么某些页面打开是空白的? + +打开 F12 看 Console 有没有红色报错。最常见原因: +- 后端没启动(`ERR_CONNECTION_REFUSED`) +- 令牌过期(401 Unauthorized)→ 清 localStorage 重新登录 + +### Q5: 怎么改颜色/样式? + +全局 CSS 变量在 `src/assets/styles/variables.css`: +- 主色 `--color-primary: #1E6BFF` +- 背景 `--color-bg: #F2F5FA` +- 改一个地方,全局生效 + +--- + +> 文档版本:v1.0 | 最后更新:2026-05-20 | 患者前端技术文档 diff --git a/上线规划文档.md b/上线规划文档.md new file mode 100644 index 0000000..051228f --- /dev/null +++ b/上线规划文档.md @@ -0,0 +1,553 @@ +# 上线全流程规划文档 + +> 当前项目是一个跑在本地的 Demo。本文档说明要成为一个**真正可上线运营的产品**,还需要做哪些工作。 + +--- + +## 目录 + +1. [文件上传与存储](#1-文件上传与存储) +2. [医生端查看与解读](#2-医生端查看与解读) +3. [历史记录与数据持久化](#3-历史记录与数据持久化) +4. [页面过渡动画](#4-页面过渡动画) +5. [联网与 App 化](#5-联网与-app-化) +6. [安全与鉴权](#6-安全与鉴权) +7. [消息推送](#7-消息推送) +8. [运维与监控](#8-运维与监控) +9. [开发流程规范化](#9-开发流程规范化) +10. [完整上线检查清单](#10-完整上线检查清单) + +--- + +## 1. 文件上传与存储 + +### 现状 + +- 患者点"上传报告",选了文件但**没有真正存到服务器** +- 后端只记录了空的 `imageUrls: []` +- 没有文件存储服务对接 + +### 需要做的事 + +#### 1.1 MinIO 对接(已在本地装了,没接代码) + +MinIO 是一个兼容 AWS S3 的对象存储服务,适合存图片、PDF 等文件。 + +**后端需要新增:** + +``` +backend/src/HealthManager.Infrastructure/Services/FileStorageService.cs ← 新建 +``` + +核心功能: +- `UploadAsync(Stream fileStream, string fileName)` → 上传到 MinIO,返回文件 URL +- `GetFileUrl(string objectKey)` → 获取文件访问地址 +- `DeleteAsync(string objectKey)` → 删除文件 + +**流程:** +``` +患者选图片 → 前端调 POST /api/files/upload → 后端接收文件流 +→ 上传到 MinIO → 得到 URL → 报告保存时把 URL 存入 imageUrls +→ 医生点查看 → 前端直接通过 URL 加载图片 +``` + +#### 1.2 新增文件上传接口 + +``` +POST /api/files/upload ← 上传单个文件,返回 { url, key } +POST /api/files/upload-multiple ← 上传多个文件 +GET /api/files/{key} ← 下载/预览文件 +DELETE /api/files/{key} ← 删除文件 +``` + +#### 1.3 前端改造 + +``` +ReportUploadPage.tsx: + 选文件 → 调上传接口 → 拿到 URL 数组 + → 调创建报告接口 { title, category, imageUrls: ["url1", "url2"] } + → 显示上传进度条 +``` + +#### 1.4 文件大小和类型限制 + +| 限制项 | 建议值 | +|--------|--------| +| 单文件最大 | 10MB | +| 允许格式 | jpg, png, pdf | +| 每人每天上限 | 20 个文件 | + +--- + +## 2. 医生端查看与解读 + +### 现状 + +- 医生能看到报告标题和状态 +- 但看不到图片(因为根本没存图) +- 解读功能比较简陋 + +### 需要做的事 + +#### 2.1 图片查看器 + +医生点报告后,应该能看到所有上传的图片: +``` +ReportDetailPage.tsx: + - 图片缩略图列表(来自 imageUrls) + - 点击放大查看 + - 支持左右翻页 +``` + +#### 2.2 报告解读界面增强 + +``` +当前:只有一个文本框,写一句话 +需要: + - 逐项填写检查结果(血压、血糖、血脂...) + - 异常项自动标红 + - 风险等级(正常/关注/异常) + - 建议措施的富文本编辑器 + - 解读完成后自动通知患者 +``` + +#### 2.3 报告历史 + +医生端需要: +- 按患者查看历史报告列表 +- 按时间/类型筛选 +- 对比不同时间点的报告变化趋势 + +--- + +## 3. 历史记录与数据持久化 + +### 现状 + +- 数据存在本地 PostgreSQL +- 没有定期备份 +- 健康数据会无限增长 + +### 需要做的事 + +#### 3.1 数据库备份 + +```bash +# 自动备份脚本(Linux 服务器上跑) +pg_dump HealthManager > /backup/health_$(date +%Y%m%d).sql + +# 每天凌晨 3 点自动执行(crontab) +0 3 * * * /opt/scripts/backup-db.sh + +# 保留最近 30 天,旧自动删除 +``` + +#### 3.2 历史数据归档 + +健康记录表会越来越大,需要归档策略: +- 1 年以上的健康数据 → 移到归档表或压缩存储 +- 3 年以上的 → 可选择性清理 +- 报告和相关文件 → 永久保留 + +#### 3.3 数据导出 + +患者应该能导出自己的数据: +``` +GET /api/health-records/export?format=csv +GET /api/health-records/export?format=pdf +``` + +--- + +## 4. 页面过渡动画 + +### 现状 + +- 有 CSS 动画定义但未完全启用 +- 页面切换比较生硬 + +### 需要做的事 + +#### 4.1 路由过渡动画 + +使用 `framer-motion`(已安装)实现: + +```tsx +// 页面进入:从右滑入 + 淡入 +// 页面离开:向左滑出 + 淡出 +// 返回时:从左滑入 + + + + + + +``` + +#### 4.2 组件级动画 + +| 场景 | 动画 | +|------|------| +| 列表加载 | 骨架屏(Skeleton)闪烁,数据来到后列表项依次弹出 | +| 卡片点击 | 缩放+阴影变化,给"按下去"的反馈 | +| 数字变化 | 健康数据变化时数字滚动动画 | +| 按钮点击 | 涟漪效果(ripple effect) | +| 消息发送 | 气泡从输入框弹入对话区 | +| 下拉刷新 | 顶部旋转加载指示器 | +| Toast 提示 | 从顶部滑入,2.5 秒后滑出 | + +#### 4.3 页面切换保留状态 + +当前切换到别的 Tab 再回来,数据会重新加载。需要: +- 首页数据缓存 30 秒,切回来直接用 +- 列表页记住滚动位置 + +--- + +## 5. 联网与 App 化 + +### 现状 + +- 前端 `npm run dev` 跑在本地 +- 后端 `dotnet run` 跑在本地 +- 只能用 `localhost` 访问 + +### 需要做的事 + +#### 5.1 部署到服务器 + +**最简单方案:一台 Linux 云服务器(阿里云/腾讯云)** + +``` +服务器配置建议: + - 2核 CPU + - 4GB 内存 + - 40GB SSD + - Ubuntu 22.04 +``` + +需要安装的东西和本地一样: +- PostgreSQL 18 +- Redis +- MinIO +- .NET 10 Runtime +- Nginx(作为反代和静态文件服务) + +#### 5.2 Nginx 配置 + +``` +把所有服务统一到一个域名下: + +https://api.chatmed.online → 后端 API (5000端口) +https://patient.chatmed.online → 患者前端 (打包成静态文件) +https://doctor.chatmed.online → 医生前端 (打包成静态文件) +https://files.chatmed.online → MinIO (9000端口) +``` + +#### 5.3 前端打包部署 + +现在跑的是开发模式(Vite dev server),上线要用生产模式: + +```bash +# 患者端 +cd frontend-patient +npm run build # 生成 dist/ 文件夹 +# 把 dist/ 里的文件放到 Nginx 的目录下 + +# 医生端同理 +cd frontend-doctor +npm run build +``` + +#### 5.4 打包成手机 App + +如果要在手机上像 App 一样用(不是浏览器打开),两种方案: + +**方案 A:PWA(推荐,最简单)** + +在网页基础上加个配置文件,用户浏览器打开后可以"添加到主屏幕",就像真的 App: + +```json +// public/manifest.json +{ + "name": "健康管家", + "short_name": "健康管家", + "start_url": "/", + "display": "standalone", // 全屏,没有浏览器地址栏 + "background_color": "#F2F5FA", + "theme_color": "#1E6BFF", + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192" }, + { "src": "/icon-512.png", "sizes": "512x512" } + ] +} +``` + +再加一个 Service Worker 实现**离线缓存**——没网也能看历史数据。 + +**方案 B:套壳 App(推荐给小白用户)** + +用 Capacitor 把网页包成一个真实的 APK/IPA: + +```bash +npm install @capacitor/core @capacitor/cli +npx cap init +npx cap add android # 生成 Android 项目 +npx cap add ios # 生成 iOS 项目 +npx cap sync # 把前端代码同步进去 +``` + +然后分别在 Android Studio 和 Xcode 里打包成 `.apk` 和 `.ipa`,可以上传到应用商店。 + +| 对比 | PWA | Capacitor 套壳 | +|------|-----|---------------| +| 开发成本 | 低(只需配置) | 中(需原生打包环境) | +| 用户安装方式 | 网页上点"添加到桌面" | 应用商店下载 | +| 离线支持 | 有 | 有 | +| 推送通知 | iOS 支持有限 | 完整支持 | +| 应用商店上架 | 不能 | 能 | +| **建议** | **先做这个,快速验证** | **验证完再做这个** | + +--- + +## 6. 安全与鉴权 + +### 现状 + +- JWT 登录,30 分钟过期 +- 密码是 SHA256(Demo 够用,上线不够) +- 没有 HTTPS +- 没有接口限流 +- 手机验证码是假的(不验证) + +### 需要做的事 + +#### 6.1 HTTPS + 域名 + +```bash +# 用 Let's Encrypt 免费申请 SSL 证书 +# 所有请求强制走 HTTPS +# 后端、前端、MinIO 全部 HTTPS +``` + +#### 6.2 密码安全 + +``` +当前方案:SHA256(password) +上线方案:bcrypt 或 Argon2(加盐哈希,防彩虹表攻击) +``` + +#### 6.3 真实短信验证码 + +接腾讯云短信 / 阿里云短信: +``` +POST /api/auth/send-sms → 调短信平台API → 用户手机收到验证码 +验证码 5 分钟过期 +同一号码 60 秒内不能重复发送 +``` + +#### 6.4 接口安全 + +| 措施 | 说明 | +|------|------| +| 限流 | 同一 IP 每分钟最多 60 次请求 | +| 登录限流 | 同一手机号 5 次失败后锁定 15 分钟 | +| 敏感操作二次验证 | 修改手机号、删除数据需要再输入验证码 | +| SQL 注入防护 | EF Core 已自带参数化查询 | +| XSS 防护 | 前端 React 已自带转义 | +| CORS | 只允许自己域名 | + +#### 6.5 数据脱敏 + +``` +日志里不能记录手机号、密码、真实姓名 +显示时手机号中间四位打星号:138****8000 +``` + +--- + +## 7. 消息推送 + +### 现状 + +- 通知存在数据库里,前端轮询拉取 +- 用户不打开 App 就看不到新消息 + +### 需要做的事 + +#### 7.1 WebSocket 实时推送 + +当前已有 SignalR(`/hubs/chat`),需要扩展到: +- 新消息实时弹窗 +- 医生解读完成 → 患者立即收到通知 +- 用药提醒时间到了 → 推送通知 + +#### 7.2 App 推送(离线也能收到) + +| 平台 | 推送服务 | +|------|---------| +| Android | Firebase Cloud Messaging (FCM) | +| iOS | Apple Push Notification Service (APNs) | +| 国内 Android | 华为/小米/OPPO 推送(不用 FCM) | + +后端需要新增一个 PushService,统一处理各类推送。 + +--- + +## 8. 运维与监控 + +### 需要做的事 + +#### 8.1 日志系统 + +``` +当前:控制台输出 +需要:Serilog 写入文件 + 集中收集 +├── 请求日志(谁、什么时候、调了什么接口) +├── 错误日志(哪出错了、堆栈信息) +└── 慢查询日志(哪些 SQL 耗时超过 500ms) +``` + +#### 8.2 健康检查 + +``` +GET /health → 检查数据库连接、Redis连接、MinIO连接 +返回 { status: "healthy", db: "ok", redis: "ok", minio: "ok" } +``` + +自动化监控:每 30 秒检查一次,挂了自动发短信/邮件报警。 + +#### 8.3 性能监控 + +``` +.NET: Application Insights 或 Prometheus +PostgreSQL: pg_stat_statements 插件 +前端: Web Vitals (LCP, FID, CLS) +``` + +#### 8.4 自动部署 + +```bash +# 当 git push 到 main 分支时,服务器自动: +1. git pull 拉取最新代码 +2. dotnet build 编译后端 +3. npm run build 编译前端 +4. 重启服务 +``` + +用 GitHub Actions 或 Jenkins 实现。 + +--- + +## 9. 开发流程规范化 + +### 当前 + +- 直接在 main 分支上改代码 +- 没有测试 +- 没有代码审查 + +### 需要建立 + +#### 9.1 分支管理 + +``` +main ← 生产环境,只有经过测试的代码才合并进来 +develop ← 开发环境 +feature/* ← 每个新功能一个分支(如 feature/file-upload) +bugfix/* ← 修 Bug 的分支 +release/* ← 准备上线的版本 +``` + +#### 9.2 提交规范 + +``` +feat: 文件上传功能 +fix: 修复医生看不到报告的bug +docs: 更新使用手册 +style: 调整首页配色 +refactor: 重构健康数据服务 +test: 添加登录接口测试 +``` + +#### 9.3 自动化测试 + +``` +单元测试:关键业务逻辑(如风险等级计算) +集成测试:API 接口(用 xUnit + WebApplicationFactory) +E2E 测试:核心用户流程(用 Playwright) +``` + +#### 9.4 代码审查 + +每个 PR 至少一个人 Review 后才能合并。 + +--- + +## 10. 完整上线检查清单 + +### 阶段一:Demo 完善(当前 → 2周内) + +- [x] 后端 API 全功能 +- [x] 患者前端全功能 +- [x] 医生前端全功能 +- [ ] 文件上传到 MinIO +- [ ] 医生端图片查看 +- [ ] 页面过渡动画 +- [ ] 上报 Bug 修复 + +### 阶段二:内部测试(2-4周) + +- [ ] 部署到测试服务器 +- [ ] 配置 HTTPS + 域名 +- [ ] 手机号验证码真实对接 +- [ ] 密码改用 bcrypt +- [ ] 接口限流 +- [ ] 3-5 个真实用户测试 +- [ ] 收集反馈,修复问题 + +### 阶段三:上线准备(4-6周) + +- [ ] 部署到生产服务器(2核4G起步) +- [ ] 数据库自动备份 +- [ ] 日志和监控就位 +- [ ] PWA 配置(可添加到手机桌面) +- [ ] 性能压测(模拟 100 个用户同时用) +- [ ] 隐私协议 + 用户协议页面 +- [ ] 操作手册终稿 + +### 阶段四:正式运营(6-8周+) + +- [ ] 上线运营 +- [ ] 用户反馈渠道 +- [ ] 定期数据备份验证 +- [ ] 根据数据做功能迭代 +- [ ] 考虑 App 应用商店上架(Capacitor 打包) +- [ ] 如果需要,接微信小程序 + +--- + +## 总结:Demo vs 上线 差异一览 + +| 维度 | 当前 Demo | 上线产品 | +|------|----------|---------| +| 文件存储 | 没存,imageUrls 为空 | MinIO 存储,返回真实 URL | +| 网络 | localhost | 域名 + HTTPS | +| 鉴权 | JWT + SHA256 密码 | JWT + bcrypt + 短信验证码 | +| 推送 | 前端轮询 | SignalR 实时 + App 离线推送 | +| 前端 | Vite dev server | Nginx 静态文件 / PWA / App | +| 数据库 | 本地 PostgreSQL | 服务器 PostgreSQL + 每日备份 | +| 日志 | 控制台 | 文件 + 集中收集 | +| 测试 | 无 | 单元 + 集成 + E2E | +| 部署 | 手动 dotnet run | 自动化 CI/CD | +| 监控 | 无 | 健康检查 + 性能 + 报警 | +| 动画 | 基础 CSS | framer-motion 全页面动画 | +| 安全 | CORS | 限流 + HTTPS + 脱敏 + 审计 | + +**最优先要做的三件事**:① 文件上传存 MinIO → ② 部署到服务器 → ③ HTTPS + 真实短信 diff --git a/技术文档.md b/技术文档.md deleted file mode 100644 index 956cab8..0000000 --- a/技术文档.md +++ /dev/null @@ -1,1517 +0,0 @@ -# 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\? | 病史列表,如 `["高血压", "冠心病"]`,在数据库存为数组 | -| StentDate | DateOnly? | 支架植入日期(患者特有) | -| StentType | string? | 支架类型(患者特有) | -| Department | string? | 科室(医生特有) | -| Title | string? | 职称,如 `"主任医师"`(医生特有) | -| Specialty | List\? | 擅长领域列表(医生特有) | -| 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\ | 服药时间点列表,如 `["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\ | 报告图片地址列表,存为数组 | -| 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 Users => Set(); - public DbSet HealthRecords => Set(); - // ... 共 15 个 - ``` - -2. **在 `OnModelCreating` 中配置表结构**: - - **JSON 列映射**:HealthRecord.Value 类型是 `JsonDocument`,配置为 PostgreSQL 的 `jsonb` 类型 - - **数组列映射**:`List` 类型的属性(如 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 的 `` 渲染子路由页面。使用 `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`)都包裹在 `` + `` 中,确保必须登录才能访问。 - ---- - -#### 各页面详细说明 - -##### 登录页 `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` | 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 健康管理平台