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

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

View File

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

View File

@@ -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 的 `<NavLink>` + `<Outlet>` 实现。
### 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<void>;
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 个统计卡片**:患者总数(蓝)、进行中问诊(绿)、待审核报告(橙)、今日随访(紫)
- **快捷操作**:患者列表、在线问诊、报告审核、随访管理(用 `<Link>` 跳转)
- **今日待办**:列出待审核报告数、进行中问诊数、今日随访数
### 患者管理
**患者列表 `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 的 `<NavLink>` 组件,`style` 回调中的 `isActive` 参数判断当前 URL 是否匹配。
---
> 文档版本v1.0 | 最后更新2026-05-20 | 医生前端技术文档

View File

@@ -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 | 患者前端技术文档

553
上线规划文档.md Normal file
View File

@@ -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
// 页面进入:从右滑入 + 淡入
// 页面离开:向左滑出 + 淡出
// 返回时:从左滑入
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.25 }}
>
<Outlet />
</motion.div>
</AnimatePresence>
```
#### 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 一样用(不是浏览器打开),两种方案:
**方案 APWA推荐最简单**
在网页基础上加个配置文件,用户浏览器打开后可以"添加到主屏幕",就像真的 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 分钟过期
- 密码是 SHA256Demo 够用,上线不够)
- 没有 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 + 真实短信

File diff suppressed because it is too large Load Diff