# 健康数据模块设计文档 ## 概述 本设计文档针对慢病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_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 addPhysicalData(@RequestBody @Valid AddPhysicalDataRequest request) { physicalDataService.addPhysicalData(request); return R.success(SuccessResultCode.SUCCESS, "添加成功", null); } @PostMapping("/list") @Operation(summary = "查询体格数据列表") public R> listPhysicalData(@RequestBody @Valid BaseQueryRequest request) { Page response = physicalDataService.listPhysicalData(request); return R.success(SuccessResultCode.SUCCESS, "查询成功", response); } @GetMapping("/bmi") @Operation(summary = "计算BMI") public R 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 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 listPhysicalData(BaseQueryRequest request) { Long userId = getCurrentUserId(); Page page = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper 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 result = physicalDataMapper.selectPage(page, wrapper); // 转换为响应对象,计算BMI List responses = result.getRecords().stream() .map(this::convertToResponse) .collect(Collectors.toList()); Page 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 { } ``` ### 血压数据模块接口 #### Controller层 ```java @RestController @RequestMapping("/blood-pressure-data") @Tag(name = "血压数据管理", description = "病人血压数据相关接口") public class BloodPressureDataController { @Autowired private BloodPressureDataService bloodPressureDataService; @PostMapping("/add") @Operation(summary = "添加血压数据") public R addBloodPressureData(@RequestBody @Valid AddBloodPressureDataRequest request) { bloodPressureDataService.addBloodPressureData(request); return R.success(SuccessResultCode.SUCCESS, "添加成功", null); } @PostMapping("/list") @Operation(summary = "查询血压数据列表") public R> listBloodPressureData(@RequestBody @Valid BaseQueryRequest request) { Page response = bloodPressureDataService.listBloodPressureData(request); return R.success(SuccessResultCode.SUCCESS, "查询成功", response); } } ``` #### Service层 ```java public interface BloodPressureDataService { void addBloodPressureData(AddBloodPressureDataRequest request); Page 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 listBloodPressureData(BaseQueryRequest request) { Long userId = getCurrentUserId(); Page page = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper 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 result = bloodPressureDataMapper.selectPage(page, wrapper); List responses = result.getRecords().stream() .map(record -> { BloodPressureDataResponse response = new BloodPressureDataResponse(); BeanUtils.copyProperties(record, response); return response; }) .collect(Collectors.toList()); Page 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 { } ``` ### 血糖数据模块接口 #### Controller层 ```java @RestController @RequestMapping("/blood-glucose-data") @Tag(name = "血糖数据管理", description = "病人血糖数据相关接口") public class BloodGlucoseDataController { @Autowired private BloodGlucoseDataService bloodGlucoseDataService; @PostMapping("/add") @Operation(summary = "添加血糖数据") public R addBloodGlucoseData(@RequestBody @Valid AddBloodGlucoseDataRequest request) { bloodGlucoseDataService.addBloodGlucoseData(request); return R.success(SuccessResultCode.SUCCESS, "添加成功", null); } @PostMapping("/list") @Operation(summary = "查询血糖数据列表") public R> listBloodGlucoseData(@RequestBody @Valid BaseQueryRequest request) { Page response = bloodGlucoseDataService.listBloodGlucoseData(request); return R.success(SuccessResultCode.SUCCESS, "查询成功", response); } } ``` #### Service层 ```java public interface BloodGlucoseDataService { void addBloodGlucoseData(AddBloodGlucoseDataRequest request); Page 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 listBloodGlucoseData(BaseQueryRequest request) { Long userId = getCurrentUserId(); Page page = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper 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 result = bloodGlucoseDataMapper.selectPage(page, wrapper); List responses = result.getRecords().stream() .map(record -> { BloodGlucoseDataResponse response = new BloodGlucoseDataResponse(); BeanUtils.copyProperties(record, response); return response; }) .collect(Collectors.toList()); Page 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 { } ``` ### 心率数据模块接口 #### Controller层 ```java @RestController @RequestMapping("/heart-rate-data") @Tag(name = "心率数据管理", description = "病人心率数据相关接口") public class HeartRateDataController { @Autowired private HeartRateDataService heartRateDataService; @PostMapping("/add") @Operation(summary = "添加心率数据") public R addHeartRateData(@RequestBody @Valid AddHeartRateDataRequest request) { heartRateDataService.addHeartRateData(request); return R.success(SuccessResultCode.SUCCESS, "添加成功", null); } @PostMapping("/list") @Operation(summary = "查询心率数据列表") public R> listHeartRateData(@RequestBody @Valid BaseQueryRequest request) { Page response = heartRateDataService.listHeartRateData(request); return R.success(SuccessResultCode.SUCCESS, "查询成功", response); } } ``` #### Service层 ```java public interface HeartRateDataService { void addHeartRateData(AddHeartRateDataRequest request); Page 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 listHeartRateData(BaseQueryRequest request) { Long userId = getCurrentUserId(); Page page = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper 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 result = heartRateDataMapper.selectPage(page, wrapper); List responses = result.getRecords().stream() .map(record -> { HeartRateDataResponse response = new HeartRateDataResponse(); BeanUtils.copyProperties(record, response); return response; }) .collect(Collectors.toList()); Page 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 { } ``` ## 安全考虑 ### 权限控制 - 患者只能访问自己的健康数据 - 医生可以访问指定患者的健康数据 - 系统管理员可以访问所有数据 ### 数据验证 - 使用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,便于前端集成