|
|
@@ -1,980 +0,0 @@
|
|
|
-# 健康数据模块设计文档
|
|
|
-
|
|
|
-## 概述
|
|
|
-
|
|
|
-本设计文档针对慢病APP增加病人用户健康数据模块的需求进行详细设计。模块包含身高、体重、BMI(自动计算)、血压、血糖(空腹、早餐后、午餐前、午餐后、晚餐前、晚餐后、睡前)、心率等健康指标的记录和管理功能。
|
|
|
-
|
|
|
-**重要更新**:根据需求,每种类型的健康数据需要独立存储和独立的接口。因此,健康数据模块将被拆分为以下4个独立子模块:
|
|
|
-
|
|
|
-1. **体格数据模块**:身高、体重数据存储,BMI自动计算
|
|
|
-2. **血压数据模块**:收缩压、舒张压数据存储
|
|
|
-3. **血糖数据模块**:空腹血糖、早餐后血糖、午餐前血糖、午餐后血糖、晚餐前血糖、晚餐后血糖、睡前血糖数据存储
|
|
|
-4. **心率数据模块**:心率数据存储
|
|
|
-
|
|
|
-每个子模块独立设计,包含独立的数据库表、数据模型、接口和服务。
|
|
|
-
|
|
|
-## 需求分析
|
|
|
-
|
|
|
-### 功能需求
|
|
|
-1. **数据录入**:支持用户录入各项健康数据
|
|
|
-2. **数据查询**:支持查询历史健康数据,支持分页查询
|
|
|
-3. **BMI计算**:根据身高体重自动计算BMI,不存储计算结果
|
|
|
-4. **数据验证**:对输入数据进行合理性校验
|
|
|
-5. **权限控制**:基于用户角色控制数据访问权限
|
|
|
-
|
|
|
-### 数据项说明
|
|
|
-- **体格数据**:
|
|
|
- - 身高:单位cm,范围100-250cm
|
|
|
- - 体重:单位kg,范围20-300kg
|
|
|
- - BMI:自动计算,身高体重/100^2,不存储
|
|
|
-- **血压数据**:
|
|
|
- - 收缩压:单位mmHg,范围60-250
|
|
|
- - 舒张压:单位mmHg,范围40-150
|
|
|
-- **血糖数据**:
|
|
|
- - 空腹血糖:早餐前测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 早餐后血糖:早餐后2小时测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 午餐前血糖:午餐前测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 午餐后血糖:午餐后2小时测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 晚餐前血糖:晚餐前测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 晚餐后血糖:晚餐后2小时测量,单位mmol/L,范围1.0-30.0
|
|
|
- - 睡前血糖:睡前测量,单位mmol/L,范围1.0-30.0
|
|
|
-- **心率数据**:
|
|
|
- - 心率:单位次/分钟,范围30-200
|
|
|
-
|
|
|
-## 设计原则
|
|
|
-
|
|
|
-### 遵循规范
|
|
|
-- 严格遵循项目结构规范(02-项目结构规范.md)
|
|
|
-- 遵循API设计规范(03-API设计规范.md)
|
|
|
-- 遵循数据库规范(07-数据库规范.md)
|
|
|
-- 遵循安全规范(08-安全规范.md)
|
|
|
-
|
|
|
-### 设计原则
|
|
|
-1. **模块独立性**:每个健康数据类型独立存储和管理
|
|
|
-2. **数据完整性**:确保数据录入的准确性和完整性
|
|
|
-3. **性能优化**:合理设计索引,支持高效查询
|
|
|
-4. **扩展性**:预留扩展空间,支持未来增加更多健康指标
|
|
|
-5. **安全性**:基于角色权限控制数据访问
|
|
|
-6. **血糖分类**:按照医疗标准对血糖进行精细分类,便于医生分析病情
|
|
|
-
|
|
|
-### 血糖测量类型枚举
|
|
|
-```java
|
|
|
-// BloodGlucoseType.java
|
|
|
-public enum BloodGlucoseType {
|
|
|
- FASTING("空腹", "早餐前测量"),
|
|
|
- AFTER_BREAKFAST("早餐后", "早餐后2小时测量"),
|
|
|
- BEFORE_LUNCH("午餐前", "午餐前测量"),
|
|
|
- AFTER_LUNCH("午餐后", "午餐后2小时测量"),
|
|
|
- BEFORE_DINNER("晚餐前", "晚餐前测量"),
|
|
|
- AFTER_DINNER("晚餐后", "晚餐后2小时测量"),
|
|
|
- BEFORE_BED("睡前", "睡前测量");
|
|
|
-
|
|
|
- private final String name;
|
|
|
- private final String description;
|
|
|
-
|
|
|
- BloodGlucoseType(String name, String description) {
|
|
|
- this.name = name;
|
|
|
- this.description = description;
|
|
|
- }
|
|
|
-
|
|
|
- // getters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-## 模块架构
|
|
|
-
|
|
|
-### 总体架构
|
|
|
-```
|
|
|
-健康数据模块
|
|
|
-├── 体格数据子模块 (PhysicalData)
|
|
|
-│ ├── 数据库表:t_physical_data
|
|
|
-│ ├── PO:PhysicalData
|
|
|
-│ ├── VO:AddPhysicalDataRequest, PhysicalDataResponse等
|
|
|
-│ ├── Controller:PhysicalDataController
|
|
|
-│ ├── Service:PhysicalDataService
|
|
|
-│ └── Mapper:PhysicalDataMapper
|
|
|
-├── 血压数据子模块 (BloodPressureData)
|
|
|
-│ ├── 数据库表:t_blood_pressure_data
|
|
|
-│ ├── PO:BloodPressureData
|
|
|
-│ ├── VO:AddBloodPressureDataRequest, BloodPressureDataResponse等
|
|
|
-│ ├── Controller:BloodPressureDataController
|
|
|
-│ ├── Service:BloodPressureDataService
|
|
|
-│ └── Mapper:BloodPressureDataMapper
|
|
|
-├── 血糖数据子模块 (BloodGlucoseData)
|
|
|
-│ ├── 数据库表:t_blood_glucose_data
|
|
|
-│ ├── PO:BloodGlucoseData
|
|
|
-│ ├── VO:AddBloodGlucoseDataRequest, BloodGlucoseDataResponse等
|
|
|
-│ ├── Controller:BloodGlucoseDataController
|
|
|
-│ ├── Service:BloodGlucoseDataService
|
|
|
-│ └── Mapper:BloodGlucoseDataMapper
|
|
|
-└── 心率数据子模块 (HeartRateData)
|
|
|
- ├── 数据库表:t_heart_rate_data
|
|
|
- ├── PO:HeartRateData
|
|
|
- ├── VO:AddHeartRateDataRequest, HeartRateDataResponse等
|
|
|
- ├── Controller:HeartRateDataController
|
|
|
- ├── Service:HeartRateDataService
|
|
|
- └── Mapper:HeartRateDataMapper
|
|
|
-```
|
|
|
-
|
|
|
-### 公共组件
|
|
|
-- 统一的查询请求基类:`BaseQueryRequest`
|
|
|
-- 统一的分页响应基类:`BasePageResponse<T>`
|
|
|
-- 统一的业务异常处理
|
|
|
-- 统一的用户权限验证
|
|
|
-
|
|
|
-## 数据库设计
|
|
|
-
|
|
|
-### 体格数据表 (t_physical_data)
|
|
|
-```sql
|
|
|
-CREATE TABLE t_physical_data (
|
|
|
- id BIGINT PRIMARY KEY COMMENT '主键ID',
|
|
|
- user_id BIGINT NOT NULL COMMENT '用户ID',
|
|
|
- height DECIMAL(5,2) COMMENT '身高(cm)',
|
|
|
- weight DECIMAL(5,2) COMMENT '体重(kg)',
|
|
|
- measure_time DATETIME NOT NULL COMMENT '测量时间',
|
|
|
- create_user BIGINT COMMENT '创建者ID',
|
|
|
- create_time DATETIME COMMENT '创建时间',
|
|
|
- update_user BIGINT COMMENT '更新者ID',
|
|
|
- update_time DATETIME COMMENT '更新时间',
|
|
|
- version INT DEFAULT 0 COMMENT '乐观锁版本号'
|
|
|
-) COMMENT '体格数据表';
|
|
|
-
|
|
|
--- 索引设计
|
|
|
-CREATE INDEX idx_physical_user_measure_time ON t_physical_data(user_id, measure_time);
|
|
|
-CREATE INDEX idx_physical_user_id ON t_physical_data(user_id);
|
|
|
-```
|
|
|
-
|
|
|
-### 血压数据表 (t_blood_pressure_data)
|
|
|
-```sql
|
|
|
-CREATE TABLE t_blood_pressure_data (
|
|
|
- id BIGINT PRIMARY KEY COMMENT '主键ID',
|
|
|
- user_id BIGINT NOT NULL COMMENT '用户ID',
|
|
|
- systolic_pressure INT COMMENT '收缩压(mmHg)',
|
|
|
- diastolic_pressure INT COMMENT '舒张压(mmHg)',
|
|
|
- measure_time DATETIME NOT NULL COMMENT '测量时间',
|
|
|
- create_user BIGINT COMMENT '创建者ID',
|
|
|
- create_time DATETIME COMMENT '创建时间',
|
|
|
- update_user BIGINT COMMENT '更新者ID',
|
|
|
- update_time DATETIME COMMENT '更新时间',
|
|
|
- version INT DEFAULT 0 COMMENT '乐观锁版本号'
|
|
|
-) COMMENT '血压数据表';
|
|
|
-
|
|
|
--- 索引设计
|
|
|
-CREATE INDEX idx_bp_user_measure_time ON t_blood_pressure_data(user_id, measure_time);
|
|
|
-CREATE INDEX idx_bp_user_id ON t_blood_pressure_data(user_id);
|
|
|
-```
|
|
|
-
|
|
|
-### 血糖数据表 (t_blood_glucose_data)
|
|
|
-```sql
|
|
|
-CREATE TABLE t_blood_glucose_data (
|
|
|
- id BIGINT PRIMARY KEY COMMENT '主键ID',
|
|
|
- user_id BIGINT NOT NULL COMMENT '用户ID',
|
|
|
- measure_time DATETIME NOT NULL COMMENT '测量时间',
|
|
|
- type VARCHAR(50) NOT NULL COMMENT '血糖测量类型',
|
|
|
- value DECIMAL(5, 2) NOT NULL COMMENT '血糖值',
|
|
|
- version INT DEFAULT 0 COMMENT '乐观锁版本号'
|
|
|
-) COMMENT '血糖数据表';
|
|
|
-
|
|
|
--- 索引设计
|
|
|
-CREATE INDEX idx_bg_user_measure_time ON t_blood_glucose_data(user_id, measure_time);
|
|
|
-CREATE INDEX idx_bg_user_id ON t_blood_glucose_data(user_id);
|
|
|
-CREATE INDEX idx_bg_type ON t_blood_glucose_data(type);
|
|
|
-```
|
|
|
-
|
|
|
-### 心率数据表 (t_heart_rate_data)
|
|
|
-```sql
|
|
|
-CREATE TABLE t_heart_rate_data (
|
|
|
- id BIGINT PRIMARY KEY COMMENT '主键ID',
|
|
|
- user_id BIGINT NOT NULL COMMENT '用户ID',
|
|
|
- heart_rate INT COMMENT '心率(次/分钟)',
|
|
|
- measure_time DATETIME NOT NULL COMMENT '测量时间',
|
|
|
- create_user BIGINT COMMENT '创建者ID',
|
|
|
- create_time DATETIME COMMENT '创建时间',
|
|
|
- update_user BIGINT COMMENT '更新者ID',
|
|
|
- update_time DATETIME COMMENT '更新时间',
|
|
|
- version INT DEFAULT 0 COMMENT '乐观锁版本号'
|
|
|
-) COMMENT '心率数据表';
|
|
|
-
|
|
|
--- 索引设计
|
|
|
-CREATE INDEX idx_hr_user_measure_time ON t_heart_rate_data(user_id, measure_time);
|
|
|
-CREATE INDEX idx_hr_user_id ON t_heart_rate_data(user_id);
|
|
|
-```
|
|
|
-
|
|
|
-### 字段说明
|
|
|
-- `id`:主键,使用雪花算法生成
|
|
|
-- `user_id`:关联用户ID
|
|
|
-- `measure_time`:数据测量时间
|
|
|
-- 各模块特有字段:
|
|
|
- - 体格数据:`height`(身高), `weight`(体重)
|
|
|
- - 血压数据:`systolic_pressure`(收缩压), `diastolic_pressure`(舒张压)
|
|
|
- - 血糖数据:`type`(血糖测量类型), `value`(血糖值)
|
|
|
- - 心率数据:`heart_rate`(心率)
|
|
|
-- 继承BaseEntity的公共字段
|
|
|
-
|
|
|
-## 数据模型设计
|
|
|
-
|
|
|
-### PO (持久化对象)
|
|
|
-
|
|
|
-#### 体格数据PO
|
|
|
-```java
|
|
|
-// PhysicalData.java
|
|
|
-@TableName("t_physical_data")
|
|
|
-public class PhysicalData extends BaseEntity {
|
|
|
- @TableField("user_id")
|
|
|
- private Long userId;
|
|
|
-
|
|
|
- @TableField("height")
|
|
|
- private BigDecimal height;
|
|
|
-
|
|
|
- @TableField("weight")
|
|
|
- private BigDecimal weight;
|
|
|
-
|
|
|
- @TableField("measure_time")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 血压数据PO
|
|
|
-```java
|
|
|
-// BloodPressureData.java
|
|
|
-@TableName("t_blood_pressure_data")
|
|
|
-public class BloodPressureData extends BaseEntity {
|
|
|
- @TableField("user_id")
|
|
|
- private Long userId;
|
|
|
-
|
|
|
- @TableField("systolic_pressure")
|
|
|
- private Integer systolicPressure;
|
|
|
-
|
|
|
- @TableField("diastolic_pressure")
|
|
|
- private Integer diastolicPressure;
|
|
|
-
|
|
|
- @TableField("measure_time")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 血糖数据PO
|
|
|
-```java
|
|
|
-// BloodGlucoseData.java
|
|
|
-@TableName("t_blood_glucose_data")
|
|
|
-public class BloodGlucoseData extends BaseEntity {
|
|
|
- @TableField("user_id")
|
|
|
- private Long userId;
|
|
|
-
|
|
|
- @TableField("measure_time")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @TableField("type")
|
|
|
- private String type;
|
|
|
-
|
|
|
- @TableField("value")
|
|
|
- private BigDecimal value;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 心率数据PO
|
|
|
-```java
|
|
|
-// HeartRateData.java
|
|
|
-@TableName("t_heart_rate_data")
|
|
|
-public class HeartRateData extends BaseEntity {
|
|
|
- @TableField("user_id")
|
|
|
- private Long userId;
|
|
|
-
|
|
|
- @TableField("heart_rate")
|
|
|
- private Integer heartRate;
|
|
|
-
|
|
|
- @TableField("measure_time")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-### VO (视图对象)
|
|
|
-
|
|
|
-#### 公共查询请求基类
|
|
|
-```java
|
|
|
-// BaseQueryRequest.java
|
|
|
-@Schema(description = "基础查询请求")
|
|
|
-public class BaseQueryRequest {
|
|
|
- @Schema(description = "页码", minimum = "1", defaultValue = "1")
|
|
|
- @Min(1)
|
|
|
- private Integer pageNum = 1;
|
|
|
-
|
|
|
- @Schema(description = "每页大小", minimum = "1", maximum = "100", defaultValue = "10")
|
|
|
- @Min(1) @Max(100)
|
|
|
- private Integer pageSize = 10;
|
|
|
-
|
|
|
- @Schema(description = "开始时间")
|
|
|
- private LocalDateTime startTime;
|
|
|
-
|
|
|
- @Schema(description = "结束时间")
|
|
|
- private LocalDateTime endTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 体格数据VO
|
|
|
-```java
|
|
|
-// AddPhysicalDataRequest.java
|
|
|
-@Schema(description = "添加体格数据请求")
|
|
|
-public class AddPhysicalDataRequest {
|
|
|
- @Schema(description = "身高(cm)", minimum = "100", maximum = "250")
|
|
|
- @DecimalMin("100") @DecimalMax("250")
|
|
|
- private BigDecimal height;
|
|
|
-
|
|
|
- @Schema(description = "体重(kg)", minimum = "20", maximum = "300")
|
|
|
- @DecimalMin("20") @DecimalMax("300")
|
|
|
- private BigDecimal weight;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- @NotNull
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-
|
|
|
-// PhysicalDataResponse.java
|
|
|
-@Schema(description = "体格数据响应")
|
|
|
-public class PhysicalDataResponse {
|
|
|
- @Schema(description = "记录ID")
|
|
|
- private Long id;
|
|
|
-
|
|
|
- @Schema(description = "身高(cm)")
|
|
|
- private BigDecimal height;
|
|
|
-
|
|
|
- @Schema(description = "体重(kg)")
|
|
|
- private BigDecimal weight;
|
|
|
-
|
|
|
- @Schema(description = "BMI")
|
|
|
- private BigDecimal bmi;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @Schema(description = "创建时间")
|
|
|
- private LocalDateTime createTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 血压数据VO
|
|
|
-```java
|
|
|
-// AddBloodPressureDataRequest.java
|
|
|
-@Schema(description = "添加血压数据请求")
|
|
|
-public class AddBloodPressureDataRequest {
|
|
|
- @Schema(description = "收缩压(mmHg)", minimum = "60", maximum = "250")
|
|
|
- @Min(60) @Max(250)
|
|
|
- private Integer systolicPressure;
|
|
|
-
|
|
|
- @Schema(description = "舒张压(mmHg)", minimum = "40", maximum = "150")
|
|
|
- @Min(40) @Max(150)
|
|
|
- private Integer diastolicPressure;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- @NotNull
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-
|
|
|
-// BloodPressureDataResponse.java
|
|
|
-@Schema(description = "血压数据响应")
|
|
|
-public class BloodPressureDataResponse {
|
|
|
- @Schema(description = "记录ID")
|
|
|
- private Long id;
|
|
|
-
|
|
|
- @Schema(description = "收缩压(mmHg)")
|
|
|
- private Integer systolicPressure;
|
|
|
-
|
|
|
- @Schema(description = "舒张压(mmHg)")
|
|
|
- private Integer diastolicPressure;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @Schema(description = "创建时间")
|
|
|
- private LocalDateTime createTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 血糖数据VO
|
|
|
-```java
|
|
|
-// AddBloodGlucoseDataRequest.java
|
|
|
-@Schema(description = "添加血糖数据请求")
|
|
|
-public class AddBloodGlucoseDataRequest {
|
|
|
- @Schema(description = "测量时间")
|
|
|
- @NotNull
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @Schema(description = "血糖测量类型")
|
|
|
- @NotNull
|
|
|
- private String type;
|
|
|
-
|
|
|
- @Schema(description = "血糖值", minimum = "1.0", maximum = "30.0")
|
|
|
- @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
- private BigDecimal value;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-
|
|
|
-// BloodGlucoseDataResponse.java
|
|
|
-@Schema(description = "血糖数据响应")
|
|
|
-public class BloodGlucoseDataResponse {
|
|
|
- @Schema(description = "记录ID")
|
|
|
- private Long id;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @Schema(description = "血糖测量类型")
|
|
|
- private String type;
|
|
|
-
|
|
|
- @Schema(description = "血糖值")
|
|
|
- private BigDecimal value;
|
|
|
-
|
|
|
- @Schema(description = "创建时间")
|
|
|
- private LocalDateTime createTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 心率数据VO
|
|
|
-```java
|
|
|
-// AddHeartRateDataRequest.java
|
|
|
-@Schema(description = "添加心率数据请求")
|
|
|
-public class AddHeartRateDataRequest {
|
|
|
- @Schema(description = "心率(次/分钟)", minimum = "30", maximum = "200")
|
|
|
- @Min(30) @Max(200)
|
|
|
- private Integer heartRate;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- @NotNull
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-
|
|
|
-// HeartRateDataResponse.java
|
|
|
-@Schema(description = "心率数据响应")
|
|
|
-public class HeartRateDataResponse {
|
|
|
- @Schema(description = "记录ID")
|
|
|
- private Long id;
|
|
|
-
|
|
|
- @Schema(description = "心率(次/分钟)")
|
|
|
- private Integer heartRate;
|
|
|
-
|
|
|
- @Schema(description = "测量时间")
|
|
|
- private LocalDateTime measureTime;
|
|
|
-
|
|
|
- @Schema(description = "创建时间")
|
|
|
- private LocalDateTime createTime;
|
|
|
-
|
|
|
- // getters and setters
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-## 接口设计
|
|
|
-
|
|
|
-### 体格数据模块接口
|
|
|
-
|
|
|
-#### Controller层
|
|
|
-```java
|
|
|
-@RestController
|
|
|
-@RequestMapping("/physical-data")
|
|
|
-@Tag(name = "体格数据管理", description = "病人体格数据相关接口")
|
|
|
-public class PhysicalDataController {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private PhysicalDataService physicalDataService;
|
|
|
-
|
|
|
- @PostMapping("/add")
|
|
|
- @Operation(summary = "添加体格数据")
|
|
|
- public R<Void> addPhysicalData(@RequestBody @Valid AddPhysicalDataRequest request) {
|
|
|
- physicalDataService.addPhysicalData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "添加成功", null);
|
|
|
- }
|
|
|
-
|
|
|
- @PostMapping("/list")
|
|
|
- @Operation(summary = "查询体格数据列表")
|
|
|
- public R<Page<PhysicalDataResponse>> listPhysicalData(@RequestBody @Valid BaseQueryRequest request) {
|
|
|
- Page<PhysicalDataResponse> response = physicalDataService.listPhysicalData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "查询成功", response);
|
|
|
- }
|
|
|
-
|
|
|
- @GetMapping("/bmi")
|
|
|
- @Operation(summary = "计算BMI")
|
|
|
- public R<BigDecimal> calculateBMI(@RequestParam @DecimalMin("100") @DecimalMax("250") BigDecimal height,
|
|
|
- @RequestParam @DecimalMin("20") @DecimalMax("300") BigDecimal weight) {
|
|
|
- BigDecimal bmi = physicalDataService.calculateBMI(height, weight);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "计算成功", bmi);
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Service层
|
|
|
-```java
|
|
|
-public interface PhysicalDataService {
|
|
|
- void addPhysicalData(AddPhysicalDataRequest request);
|
|
|
- Page<PhysicalDataResponse> listPhysicalData(BaseQueryRequest request);
|
|
|
- BigDecimal calculateBMI(BigDecimal height, BigDecimal weight);
|
|
|
-}
|
|
|
-
|
|
|
-@Service
|
|
|
-public class PhysicalDataServiceImpl implements PhysicalDataService {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private PhysicalDataMapper physicalDataMapper;
|
|
|
-
|
|
|
- @Override
|
|
|
- public void addPhysicalData(AddPhysicalDataRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- PhysicalData physicalData = new PhysicalData();
|
|
|
- BeanUtils.copyProperties(request, physicalData);
|
|
|
- physicalData.setUserId(userId);
|
|
|
-
|
|
|
- physicalDataMapper.insert(physicalData);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Page<PhysicalDataResponse> listPhysicalData(BaseQueryRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- Page<PhysicalData> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
- LambdaQueryWrapper<PhysicalData> wrapper = new LambdaQueryWrapper<>();
|
|
|
- wrapper.eq(PhysicalData::getUserId, userId)
|
|
|
- .ge(request.getStartTime() != null, PhysicalData::getMeasureTime, request.getStartTime())
|
|
|
- .le(request.getEndTime() != null, PhysicalData::getMeasureTime, request.getEndTime())
|
|
|
- .orderByDesc(PhysicalData::getMeasureTime);
|
|
|
-
|
|
|
- Page<PhysicalData> result = physicalDataMapper.selectPage(page, wrapper);
|
|
|
-
|
|
|
- // 转换为响应对象,计算BMI
|
|
|
- List<PhysicalDataResponse> responses = result.getRecords().stream()
|
|
|
- .map(this::convertToResponse)
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- Page<PhysicalDataResponse> responsePage = new Page<>();
|
|
|
- responsePage.setRecords(responses);
|
|
|
- responsePage.setCurrent(result.getCurrent());
|
|
|
- responsePage.setSize(result.getSize());
|
|
|
- responsePage.setTotal(result.getTotal());
|
|
|
-
|
|
|
- return responsePage;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public BigDecimal calculateBMI(BigDecimal height, BigDecimal weight) {
|
|
|
- if (height == null || weight == null || height.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
- return BigDecimal.ZERO;
|
|
|
- }
|
|
|
- // BMI = 体重(kg) / [身高(m)]^2
|
|
|
- BigDecimal heightInMeters = height.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
|
|
- return weight.divide(heightInMeters.pow(2), 2, RoundingMode.HALF_UP);
|
|
|
- }
|
|
|
-
|
|
|
- private PhysicalDataResponse convertToResponse(PhysicalData physicalData) {
|
|
|
- PhysicalDataResponse response = new PhysicalDataResponse();
|
|
|
- BeanUtils.copyProperties(physicalData, response);
|
|
|
- // 计算BMI
|
|
|
- response.setBmi(calculateBMI(physicalData.getHeight(), physicalData.getWeight()));
|
|
|
- return response;
|
|
|
- }
|
|
|
-
|
|
|
- private Long getCurrentUserId() {
|
|
|
- // 从安全上下文中获取当前用户ID
|
|
|
- return 1L; // 临时实现
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Mapper层
|
|
|
-```java
|
|
|
-@Mapper
|
|
|
-public interface PhysicalDataMapper extends BaseMapper<PhysicalData> {
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-### 血压数据模块接口
|
|
|
-
|
|
|
-#### Controller层
|
|
|
-```java
|
|
|
-@RestController
|
|
|
-@RequestMapping("/blood-pressure-data")
|
|
|
-@Tag(name = "血压数据管理", description = "病人血压数据相关接口")
|
|
|
-public class BloodPressureDataController {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private BloodPressureDataService bloodPressureDataService;
|
|
|
-
|
|
|
- @PostMapping("/add")
|
|
|
- @Operation(summary = "添加血压数据")
|
|
|
- public R<Void> addBloodPressureData(@RequestBody @Valid AddBloodPressureDataRequest request) {
|
|
|
- bloodPressureDataService.addBloodPressureData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "添加成功", null);
|
|
|
- }
|
|
|
-
|
|
|
- @PostMapping("/list")
|
|
|
- @Operation(summary = "查询血压数据列表")
|
|
|
- public R<Page<BloodPressureDataResponse>> listBloodPressureData(@RequestBody @Valid BaseQueryRequest request) {
|
|
|
- Page<BloodPressureDataResponse> response = bloodPressureDataService.listBloodPressureData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "查询成功", response);
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Service层
|
|
|
-```java
|
|
|
-public interface BloodPressureDataService {
|
|
|
- void addBloodPressureData(AddBloodPressureDataRequest request);
|
|
|
- Page<BloodPressureDataResponse> listBloodPressureData(BaseQueryRequest request);
|
|
|
-}
|
|
|
-
|
|
|
-@Service
|
|
|
-public class BloodPressureDataServiceImpl implements BloodPressureDataService {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private BloodPressureDataMapper bloodPressureDataMapper;
|
|
|
-
|
|
|
- @Override
|
|
|
- public void addBloodPressureData(AddBloodPressureDataRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- BloodPressureData bloodPressureData = new BloodPressureData();
|
|
|
- BeanUtils.copyProperties(request, bloodPressureData);
|
|
|
- bloodPressureData.setUserId(userId);
|
|
|
-
|
|
|
- bloodPressureDataMapper.insert(bloodPressureData);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Page<BloodPressureDataResponse> listBloodPressureData(BaseQueryRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- Page<BloodPressureData> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
- LambdaQueryWrapper<BloodPressureData> wrapper = new LambdaQueryWrapper<>();
|
|
|
- wrapper.eq(BloodPressureData::getUserId, userId)
|
|
|
- .ge(request.getStartTime() != null, BloodPressureData::getMeasureTime, request.getStartTime())
|
|
|
- .le(request.getEndTime() != null, BloodPressureData::getMeasureTime, request.getEndTime())
|
|
|
- .orderByDesc(BloodPressureData::getMeasureTime);
|
|
|
-
|
|
|
- Page<BloodPressureData> result = bloodPressureDataMapper.selectPage(page, wrapper);
|
|
|
-
|
|
|
- List<BloodPressureDataResponse> responses = result.getRecords().stream()
|
|
|
- .map(record -> {
|
|
|
- BloodPressureDataResponse response = new BloodPressureDataResponse();
|
|
|
- BeanUtils.copyProperties(record, response);
|
|
|
- return response;
|
|
|
- })
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- Page<BloodPressureDataResponse> responsePage = new Page<>();
|
|
|
- responsePage.setRecords(responses);
|
|
|
- responsePage.setCurrent(result.getCurrent());
|
|
|
- responsePage.setSize(result.getSize());
|
|
|
- responsePage.setTotal(result.getTotal());
|
|
|
-
|
|
|
- return responsePage;
|
|
|
- }
|
|
|
-
|
|
|
- private Long getCurrentUserId() {
|
|
|
- // 从安全上下文中获取当前用户ID
|
|
|
- return 1L; // 临时实现
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Mapper层
|
|
|
-```java
|
|
|
-@Mapper
|
|
|
-public interface BloodPressureDataMapper extends BaseMapper<BloodPressureData> {
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-### 血糖数据模块接口
|
|
|
-
|
|
|
-#### Controller层
|
|
|
-```java
|
|
|
-@RestController
|
|
|
-@RequestMapping("/blood-glucose-data")
|
|
|
-@Tag(name = "血糖数据管理", description = "病人血糖数据相关接口")
|
|
|
-public class BloodGlucoseDataController {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private BloodGlucoseDataService bloodGlucoseDataService;
|
|
|
-
|
|
|
- @PostMapping("/add")
|
|
|
- @Operation(summary = "添加血糖数据")
|
|
|
- public R<Void> addBloodGlucoseData(@RequestBody @Valid AddBloodGlucoseDataRequest request) {
|
|
|
- bloodGlucoseDataService.addBloodGlucoseData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "添加成功", null);
|
|
|
- }
|
|
|
-
|
|
|
- @PostMapping("/list")
|
|
|
- @Operation(summary = "查询血糖数据列表")
|
|
|
- public R<Page<BloodGlucoseDataResponse>> listBloodGlucoseData(@RequestBody @Valid BaseQueryRequest request) {
|
|
|
- Page<BloodGlucoseDataResponse> response = bloodGlucoseDataService.listBloodGlucoseData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "查询成功", response);
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Service层
|
|
|
-```java
|
|
|
-public interface BloodGlucoseDataService {
|
|
|
- void addBloodGlucoseData(AddBloodGlucoseDataRequest request);
|
|
|
- Page<BloodGlucoseDataResponse> listBloodGlucoseData(BaseQueryRequest request);
|
|
|
-}
|
|
|
-
|
|
|
-@Service
|
|
|
-public class BloodGlucoseDataServiceImpl implements BloodGlucoseDataService {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private BloodGlucoseDataMapper bloodGlucoseDataMapper;
|
|
|
-
|
|
|
- @Override
|
|
|
- public void addBloodGlucoseData(AddBloodGlucoseDataRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- BloodGlucoseData bloodGlucoseData = new BloodGlucoseData();
|
|
|
- BeanUtils.copyProperties(request, bloodGlucoseData);
|
|
|
- bloodGlucoseData.setUserId(userId);
|
|
|
-
|
|
|
- bloodGlucoseDataMapper.insert(bloodGlucoseData);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Page<BloodGlucoseDataResponse> listBloodGlucoseData(BaseQueryRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- Page<BloodGlucoseData> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
- LambdaQueryWrapper<BloodGlucoseData> wrapper = new LambdaQueryWrapper<>();
|
|
|
- wrapper.eq(BloodGlucoseData::getUserId, userId)
|
|
|
- .ge(request.getStartTime() != null, BloodGlucoseData::getMeasureTime, request.getStartTime())
|
|
|
- .le(request.getEndTime() != null, BloodGlucoseData::getMeasureTime, request.getEndTime())
|
|
|
- .orderByDesc(BloodGlucoseData::getMeasureTime);
|
|
|
-
|
|
|
- Page<BloodGlucoseData> result = bloodGlucoseDataMapper.selectPage(page, wrapper);
|
|
|
-
|
|
|
- List<BloodGlucoseDataResponse> responses = result.getRecords().stream()
|
|
|
- .map(record -> {
|
|
|
- BloodGlucoseDataResponse response = new BloodGlucoseDataResponse();
|
|
|
- BeanUtils.copyProperties(record, response);
|
|
|
- return response;
|
|
|
- })
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- Page<BloodGlucoseDataResponse> responsePage = new Page<>();
|
|
|
- responsePage.setRecords(responses);
|
|
|
- responsePage.setCurrent(result.getCurrent());
|
|
|
- responsePage.setSize(result.getSize());
|
|
|
- responsePage.setTotal(result.getTotal());
|
|
|
-
|
|
|
- return responsePage;
|
|
|
- }
|
|
|
-
|
|
|
- private Long getCurrentUserId() {
|
|
|
- // 从安全上下文中获取当前用户ID
|
|
|
- return 1L; // 临时实现
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Mapper层
|
|
|
-```java
|
|
|
-@Mapper
|
|
|
-public interface BloodGlucoseDataMapper extends BaseMapper<BloodGlucoseData> {
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-### 心率数据模块接口
|
|
|
-
|
|
|
-#### Controller层
|
|
|
-```java
|
|
|
-@RestController
|
|
|
-@RequestMapping("/heart-rate-data")
|
|
|
-@Tag(name = "心率数据管理", description = "病人心率数据相关接口")
|
|
|
-public class HeartRateDataController {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private HeartRateDataService heartRateDataService;
|
|
|
-
|
|
|
- @PostMapping("/add")
|
|
|
- @Operation(summary = "添加心率数据")
|
|
|
- public R<Void> addHeartRateData(@RequestBody @Valid AddHeartRateDataRequest request) {
|
|
|
- heartRateDataService.addHeartRateData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "添加成功", null);
|
|
|
- }
|
|
|
-
|
|
|
- @PostMapping("/list")
|
|
|
- @Operation(summary = "查询心率数据列表")
|
|
|
- public R<Page<HeartRateDataResponse>> listHeartRateData(@RequestBody @Valid BaseQueryRequest request) {
|
|
|
- Page<HeartRateDataResponse> response = heartRateDataService.listHeartRateData(request);
|
|
|
- return R.success(SuccessResultCode.SUCCESS, "查询成功", response);
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Service层
|
|
|
-```java
|
|
|
-public interface HeartRateDataService {
|
|
|
- void addHeartRateData(AddHeartRateDataRequest request);
|
|
|
- Page<HeartRateDataResponse> listHeartRateData(BaseQueryRequest request);
|
|
|
-}
|
|
|
-
|
|
|
-@Service
|
|
|
-public class HeartRateDataServiceImpl implements HeartRateDataService {
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private HeartRateDataMapper heartRateDataMapper;
|
|
|
-
|
|
|
- @Override
|
|
|
- public void addHeartRateData(AddHeartRateDataRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- HeartRateData heartRateData = new HeartRateData();
|
|
|
- BeanUtils.copyProperties(request, heartRateData);
|
|
|
- heartRateData.setUserId(userId);
|
|
|
-
|
|
|
- heartRateDataMapper.insert(heartRateData);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Page<HeartRateDataResponse> listHeartRateData(BaseQueryRequest request) {
|
|
|
- Long userId = getCurrentUserId();
|
|
|
-
|
|
|
- Page<HeartRateData> page = new Page<>(request.getPageNum(), request.getPageSize());
|
|
|
- LambdaQueryWrapper<HeartRateData> wrapper = new LambdaQueryWrapper<>();
|
|
|
- wrapper.eq(HeartRateData::getUserId, userId)
|
|
|
- .ge(request.getStartTime() != null, HeartRateData::getMeasureTime, request.getStartTime())
|
|
|
- .le(request.getEndTime() != null, HeartRateData::getMeasureTime, request.getEndTime())
|
|
|
- .orderByDesc(HeartRateData::getMeasureTime);
|
|
|
-
|
|
|
- Page<HeartRateData> result = heartRateDataMapper.selectPage(page, wrapper);
|
|
|
-
|
|
|
- List<HeartRateDataResponse> responses = result.getRecords().stream()
|
|
|
- .map(record -> {
|
|
|
- HeartRateDataResponse response = new HeartRateDataResponse();
|
|
|
- BeanUtils.copyProperties(record, response);
|
|
|
- return response;
|
|
|
- })
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- Page<HeartRateDataResponse> responsePage = new Page<>();
|
|
|
- responsePage.setRecords(responses);
|
|
|
- responsePage.setCurrent(result.getCurrent());
|
|
|
- responsePage.setSize(result.getSize());
|
|
|
- responsePage.setTotal(result.getTotal());
|
|
|
-
|
|
|
- return responsePage;
|
|
|
- }
|
|
|
-
|
|
|
- private Long getCurrentUserId() {
|
|
|
- // 从安全上下文中获取当前用户ID
|
|
|
- return 1L; // 临时实现
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### Mapper层
|
|
|
-```java
|
|
|
-@Mapper
|
|
|
-public interface HeartRateDataMapper extends BaseMapper<HeartRateData> {
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-## 安全考虑
|
|
|
-
|
|
|
-### 权限控制
|
|
|
-- 患者只能访问自己的健康数据
|
|
|
-- 医生可以访问指定患者的健康数据
|
|
|
-- 系统管理员可以访问所有数据
|
|
|
-
|
|
|
-### 数据验证
|
|
|
-- 使用Bean Validation进行输入校验
|
|
|
-- 业务逻辑层进行数据合理性校验(如血压高低压关系)
|
|
|
-
|
|
|
-## 性能优化
|
|
|
-
|
|
|
-### 索引设计
|
|
|
-- 用户ID + 测量时间复合索引,支持时间范围查询
|
|
|
-- 用户ID索引,支持用户数据隔离
|
|
|
-
|
|
|
-### 查询优化
|
|
|
-- 分页查询,避免一次性加载大量数据
|
|
|
-- 支持时间范围过滤,减少数据扫描量
|
|
|
-
|
|
|
-## 扩展性考虑
|
|
|
-
|
|
|
-### 未来扩展
|
|
|
-- 支持更多健康指标(如血脂、尿酸等)
|
|
|
-- 支持数据统计和趋势分析
|
|
|
-- 支持数据导出功能
|
|
|
-- 支持批量数据导入
|
|
|
-
|
|
|
-### 版本控制
|
|
|
-- API版本控制,为未来升级预留空间
|
|
|
-- 数据库表结构设计考虑向后兼容
|
|
|
-
|
|
|
-## 实施计划
|
|
|
-
|
|
|
-本设计文档为第一阶段输出,后续阶段将包括:
|
|
|
-1. **阶段2**:创建详细的API接口规范文档(包含所有4个子模块的完整API文档)
|
|
|
-2. **阶段3**:创建数据库表结构和迁移脚本文档
|
|
|
-3. **阶段4**:创建数据模型(PO/VO)详细设计文档
|
|
|
-4. **阶段5**:创建单元测试计划和集成测试用例
|
|
|
-5. **阶段6**:创建业务逻辑和异常处理设计文档
|
|
|
-6. **阶段7**:创建部署和运维文档
|
|
|
-
|
|
|
-每个阶段都会生成独立的中间文件,确保设计过程可审查和可追溯。
|
|
|
-
|
|
|
-### 实施顺序建议
|
|
|
-1. 优先实施体格数据模块(包含BMI计算功能)
|
|
|
-2. 其次实施血压数据模块
|
|
|
-3. 然后实施血糖数据模块
|
|
|
-4. 最后实施心率数据模块
|
|
|
-
|
|
|
-这样可以确保核心功能(BMI计算)优先实现,同时为其他模块提供参考模板。
|
|
|
-
|
|
|
-## 风险评估
|
|
|
-
|
|
|
-### 技术风险
|
|
|
-- 数据精度问题:使用BigDecimal确保数值计算精度
|
|
|
-- 并发问题:使用乐观锁防止并发更新冲突
|
|
|
-
|
|
|
-### 业务风险
|
|
|
-- 数据隐私:确保用户数据安全,遵循医疗数据保护规范
|
|
|
-- 数据准确性:通过校验和审核机制确保数据质量
|
|
|
-
|
|
|
-## 总结
|
|
|
-
|
|
|
-本设计遵循项目现有规范,提供了完整的健康数据模块设计方案。设计考虑了数据完整性、安全性、性能和扩展性,为慢病APP的健康数据管理提供了坚实的基础。
|
|
|
-
|
|
|
-**关键特性:**
|
|
|
-- **模块化设计**:将健康数据拆分为4个独立子模块,每个模块独立存储和管理
|
|
|
-- **数据隔离**:每种健康数据类型使用独立的数据库表,确保数据隔离和性能优化
|
|
|
-- **统一架构**:所有子模块遵循相同的架构模式,便于维护和扩展
|
|
|
-- **BMI计算**:体格数据模块提供BMI自动计算功能,不存储计算结果
|
|
|
-- **血糖精细分类**:血糖数据按7个标准医疗时间点分类,便于医生诊断分析
|
|
|
-- **权限控制**:基于用户角色确保数据访问安全
|
|
|
-- **分页查询**:支持时间范围过滤和分页查询,提高查询性能
|
|
|
-
|
|
|
-**设计优势:**
|
|
|
-1. **高内聚低耦合**:每个模块职责单一,易于维护
|
|
|
-2. **扩展性强**:新增健康数据类型只需按照模板添加新模块
|
|
|
-3. **性能优化**:独立表设计避免了大数据量查询时的性能问题
|
|
|
-4. **医疗专业性**:血糖数据按标准医疗时间点分类,便于医生诊断
|
|
|
-5. **数据一致性**:每种数据类型独立管理,确保数据一致性
|
|
|
-6. **接口清晰**:每个模块提供独立的API,便于前端集成
|