|
@@ -0,0 +1,1039 @@
|
|
|
|
|
+# 健康数据模块设计文档
|
|
|
|
|
+
|
|
|
|
|
+## 概述
|
|
|
|
|
+
|
|
|
|
|
+本设计文档针对慢病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',
|
|
|
|
|
+ fasting_blood_glucose DECIMAL(4,2) COMMENT '空腹血糖(mmol/L)',
|
|
|
|
|
+ after_breakfast_blood_glucose DECIMAL(4,2) COMMENT '早餐后血糖(mmol/L)',
|
|
|
|
|
+ before_lunch_blood_glucose DECIMAL(4,2) COMMENT '午餐前血糖(mmol/L)',
|
|
|
|
|
+ after_lunch_blood_glucose DECIMAL(4,2) COMMENT '午餐后血糖(mmol/L)',
|
|
|
|
|
+ before_dinner_blood_glucose DECIMAL(4,2) COMMENT '晚餐前血糖(mmol/L)',
|
|
|
|
|
+ after_dinner_blood_glucose DECIMAL(4,2) COMMENT '晚餐后血糖(mmol/L)',
|
|
|
|
|
+ before_bed_blood_glucose DECIMAL(4,2) COMMENT '睡前血糖(mmol/L)',
|
|
|
|
|
+ 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_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);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 心率数据表 (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`(舒张压)
|
|
|
|
|
+ - 血糖数据:`fasting_blood_glucose`(空腹血糖), `after_breakfast_blood_glucose`(早餐后血糖), `before_lunch_blood_glucose`(午餐前血糖), `after_lunch_blood_glucose`(午餐后血糖), `before_dinner_blood_glucose`(晚餐前血糖), `after_dinner_blood_glucose`(晚餐后血糖), `before_bed_blood_glucose`(睡前血糖)
|
|
|
|
|
+ - 心率数据:`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("fasting_blood_glucose")
|
|
|
|
|
+ private BigDecimal fastingBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("after_breakfast_blood_glucose")
|
|
|
|
|
+ private BigDecimal afterBreakfastBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("before_lunch_blood_glucose")
|
|
|
|
|
+ private BigDecimal beforeLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("after_lunch_blood_glucose")
|
|
|
|
|
+ private BigDecimal afterLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("before_dinner_blood_glucose")
|
|
|
|
|
+ private BigDecimal beforeDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("after_dinner_blood_glucose")
|
|
|
|
|
+ private BigDecimal afterDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("before_bed_blood_glucose")
|
|
|
|
|
+ private BigDecimal beforeBedBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @TableField("measure_time")
|
|
|
|
|
+ private LocalDateTime measureTime;
|
|
|
|
|
+
|
|
|
|
|
+ // 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 = "空腹血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal fastingBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "早餐后血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal afterBreakfastBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "午餐前血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal beforeLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "午餐后血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal afterLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "晚餐前血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal beforeDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "晚餐后血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal afterDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "睡前血糖(mmol/L)", minimum = "1.0", maximum = "30.0")
|
|
|
|
|
+ @DecimalMin("1.0") @DecimalMax("30.0")
|
|
|
|
|
+ private BigDecimal beforeBedBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "测量时间")
|
|
|
|
|
+ @NotNull
|
|
|
|
|
+ private LocalDateTime measureTime;
|
|
|
|
|
+
|
|
|
|
|
+ // getters and setters
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// BloodGlucoseDataResponse.java
|
|
|
|
|
+@Schema(description = "血糖数据响应")
|
|
|
|
|
+public class BloodGlucoseDataResponse {
|
|
|
|
|
+ @Schema(description = "记录ID")
|
|
|
|
|
+ private Long id;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "空腹血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal fastingBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "早餐后血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal afterBreakfastBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "午餐前血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal beforeLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "午餐后血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal afterLunchBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "晚餐前血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal beforeDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "晚餐后血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal afterDinnerBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "睡前血糖(mmol/L)")
|
|
|
|
|
+ private BigDecimal beforeBedBloodGlucose;
|
|
|
|
|
+
|
|
|
|
|
+ @Schema(description = "测量时间")
|
|
|
|
|
+ private LocalDateTime measureTime;
|
|
|
|
|
+
|
|
|
|
|
+ @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,便于前端集成</content>
|
|
|
|
|
+<parameter name="filePath">d:\慢病APP\ChronicDiseaseApp\docs\HealthDataModuleDesign.md
|