Explorar o código

feat(patient-data): 新增患者数据概览控制器及相关响应模型,支持获取最新身体数据

mcbaiyun hai 3 semanas
pai
achega
fea1ea905e

+ 184 - 0
src/main/java/work/baiyun/chronicdiseaseapp/controller/PatientDataOverviewController.java

@@ -0,0 +1,184 @@
+package work.baiyun.chronicdiseaseapp.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import work.baiyun.chronicdiseaseapp.common.R;
+import work.baiyun.chronicdiseaseapp.model.vo.PatientLatestOverviewResponse;
+import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
+import work.baiyun.chronicdiseaseapp.model.vo.PhysicalDataResponse;
+import work.baiyun.chronicdiseaseapp.model.vo.BloodPressureDataResponse;
+import work.baiyun.chronicdiseaseapp.model.vo.BloodGlucoseDataResponse;
+import work.baiyun.chronicdiseaseapp.model.vo.HeartRateDataResponse;
+import work.baiyun.chronicdiseaseapp.service.PhysicalDataService;
+import work.baiyun.chronicdiseaseapp.service.BloodPressureDataService;
+import work.baiyun.chronicdiseaseapp.service.BloodGlucoseDataService;
+import work.baiyun.chronicdiseaseapp.service.HeartRateDataService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@RestController
+@RequestMapping("/patient-data")
+@Tag(name = "患者数据概览 (Patient Data Overview)", description = "患者可以请求自己最新的身高/体重/BMI/血压/血糖/心率数据概览")
+public class PatientDataOverviewController {
+
+    private static final Logger logger = LoggerFactory.getLogger(PatientDataOverviewController.class);
+
+    @Autowired
+    private PhysicalDataService physicalDataService;
+    @Autowired
+    private BloodPressureDataService bloodPressureDataService;
+    @Autowired
+    private BloodGlucoseDataService bloodGlucoseDataService;
+    @Autowired
+    private HeartRateDataService heartRateDataService;
+    @Autowired
+    private work.baiyun.chronicdiseaseapp.service.UserBindingService userBindingService;
+
+    @Operation(summary = "获取患者最新数据概览", description = "返回患者最新的身高/体重/BMI/血压/血糖/心率数据")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功返回最新数据概览",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = PatientLatestOverviewResponse.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
+    @PostMapping(path = "/overview/latest", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    public R<?> latestOverview(@RequestBody(required = false) BaseQueryRequest req) {
+        try {
+            if (req == null) {
+                req = new BaseQueryRequest();
+            }
+            // We only need the most recent item for each type
+            req.setPageNum(1);
+            req.setPageSize(1);
+
+            PatientLatestOverviewResponse resp = new PatientLatestOverviewResponse();
+
+            Page<PhysicalDataResponse> pPage = physicalDataService.listPhysicalData(req);
+            if (pPage != null && pPage.getRecords() != null && !pPage.getRecords().isEmpty()) {
+                PhysicalDataResponse p = pPage.getRecords().get(0);
+                resp.setHeight(p.getHeight());
+                resp.setWeight(p.getWeight());
+                resp.setBmi(p.getBmi());
+                resp.setPhysicalMeasureTime(p.getMeasureTime());
+            }
+
+            Page<BloodPressureDataResponse> bpPage = bloodPressureDataService.listBloodPressureData(req);
+            if (bpPage != null && bpPage.getRecords() != null && !bpPage.getRecords().isEmpty()) {
+                BloodPressureDataResponse bp = bpPage.getRecords().get(0);
+                resp.setSystolicPressure(bp.getSystolicPressure());
+                resp.setDiastolicPressure(bp.getDiastolicPressure());
+                resp.setBloodPressureMeasureTime(bp.getMeasureTime());
+            }
+
+            Page<BloodGlucoseDataResponse> bgPage = bloodGlucoseDataService.listBloodGlucoseData(req);
+            if (bgPage != null && bgPage.getRecords() != null && !bgPage.getRecords().isEmpty()) {
+                BloodGlucoseDataResponse bg = bgPage.getRecords().get(0);
+                resp.setBloodGlucoseType(bg.getType());
+                resp.setBloodGlucose(bg.getValue());
+                resp.setBloodGlucoseMeasureTime(bg.getMeasureTime());
+            }
+
+            Page<HeartRateDataResponse> hrPage = heartRateDataService.listHeartRateData(req);
+            if (hrPage != null && hrPage.getRecords() != null && !hrPage.getRecords().isEmpty()) {
+                HeartRateDataResponse hr = hrPage.getRecords().get(0);
+                resp.setHeartRate(hr.getHeartRate());
+                resp.setHeartRateMeasureTime(hr.getMeasureTime());
+            }
+
+            return R.success(200, "ok", resp);
+        } catch (Exception e) {
+            logger.error("get latest overview failed", e);
+            return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
+        }
+    }
+
+    @Operation(summary = "绑定方查询患者的最新数据概览", description = "绑定方(医生/家属)查询患者最新的身高/体重/BMI/血压/血糖/心率数据(需有绑定关系)")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功返回最新数据概览",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = PatientLatestOverviewResponse.class))),
+        @ApiResponse(responseCode = "403", description = "无权限访问",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
+    @PostMapping(path = "/overview/latest-by-bound-user", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    public R<?> latestOverviewByBoundUser(Long patientUserId, String bindingType, @RequestBody(required = false) BaseQueryRequest req) {
+        try {
+            Long boundUserId = work.baiyun.chronicdiseaseapp.util.SecurityUtils.getCurrentUserId();
+
+            if (patientUserId == null) {
+                return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), ErrorCode.PARAMETER_ERROR.getMessage());
+            }
+
+            work.baiyun.chronicdiseaseapp.model.vo.CheckUserBindingRequest checkReq = new work.baiyun.chronicdiseaseapp.model.vo.CheckUserBindingRequest();
+            checkReq.setPatientUserId(patientUserId);
+            checkReq.setBoundUserId(boundUserId);
+            work.baiyun.chronicdiseaseapp.model.vo.CheckUserBindingResponse checkResp = userBindingService.checkUserBinding(checkReq);
+            if (!Boolean.TRUE.equals(checkResp.getExists())) {
+                return R.fail(ErrorCode.DATA_ACCESS_DENIED.getCode(), "当前用户与目标患者未建立绑定关系");
+            }
+            if ((bindingType == null || bindingType.isEmpty()) && checkResp.getBindingType() != null) {
+                bindingType = checkResp.getBindingType();
+            }
+
+            if (req == null) {
+                req = new BaseQueryRequest();
+            }
+            req.setPageNum(1);
+            req.setPageSize(1);
+
+            PatientLatestOverviewResponse resp = new PatientLatestOverviewResponse();
+
+            Page<PhysicalDataResponse> pPage = physicalDataService.listPhysicalDataByPatient(patientUserId, bindingType, req);
+            if (pPage != null && pPage.getRecords() != null && !pPage.getRecords().isEmpty()) {
+                PhysicalDataResponse p = pPage.getRecords().get(0);
+                resp.setHeight(p.getHeight());
+                resp.setWeight(p.getWeight());
+                resp.setBmi(p.getBmi());
+                resp.setPhysicalMeasureTime(p.getMeasureTime());
+            }
+
+            Page<BloodPressureDataResponse> bpPage = bloodPressureDataService.listBloodPressureDataByPatient(patientUserId, bindingType, req);
+            if (bpPage != null && bpPage.getRecords() != null && !bpPage.getRecords().isEmpty()) {
+                BloodPressureDataResponse bp = bpPage.getRecords().get(0);
+                resp.setSystolicPressure(bp.getSystolicPressure());
+                resp.setDiastolicPressure(bp.getDiastolicPressure());
+                resp.setBloodPressureMeasureTime(bp.getMeasureTime());
+            }
+
+            Page<BloodGlucoseDataResponse> bgPage = bloodGlucoseDataService.listBloodGlucoseDataByPatient(patientUserId, bindingType, req);
+            if (bgPage != null && bgPage.getRecords() != null && !bgPage.getRecords().isEmpty()) {
+                BloodGlucoseDataResponse bg = bgPage.getRecords().get(0);
+                resp.setBloodGlucoseType(bg.getType());
+                resp.setBloodGlucose(bg.getValue());
+                resp.setBloodGlucoseMeasureTime(bg.getMeasureTime());
+            }
+
+            Page<HeartRateDataResponse> hrPage = heartRateDataService.listHeartRateDataByPatient(patientUserId, bindingType, req);
+            if (hrPage != null && hrPage.getRecords() != null && !hrPage.getRecords().isEmpty()) {
+                HeartRateDataResponse hr = hrPage.getRecords().get(0);
+                resp.setHeartRate(hr.getHeartRate());
+                resp.setHeartRateMeasureTime(hr.getMeasureTime());
+            }
+
+            return R.success(200, "ok", resp);
+        } catch (Exception e) {
+            logger.error("get latest overview by bound user failed", e);
+            return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
+        }
+    }
+}

+ 47 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/vo/PatientLatestOverviewResponse.java

@@ -0,0 +1,47 @@
+package work.baiyun.chronicdiseaseapp.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "患者最新数据概览响应")
+@Data
+public class PatientLatestOverviewResponse {
+    @Schema(description = "身高(cm)")
+    private BigDecimal height;
+
+    @Schema(description = "体重(kg)")
+    private BigDecimal weight;
+
+    @Schema(description = "BMI")
+    private BigDecimal bmi;
+
+    @Schema(description = "体格测量时间")
+    private LocalDateTime physicalMeasureTime;
+
+    @Schema(description = "收缩压(mmHg)")
+    private Integer systolicPressure;
+
+    @Schema(description = "舒张压(mmHg)")
+    private Integer diastolicPressure;
+
+    @Schema(description = "血压测量时间")
+    private LocalDateTime bloodPressureMeasureTime;
+
+    @Schema(description = "血糖测量类型")
+    private String bloodGlucoseType;
+
+    @Schema(description = "血糖值")
+    private BigDecimal bloodGlucose;
+
+    @Schema(description = "血糖测量时间")
+    private LocalDateTime bloodGlucoseMeasureTime;
+
+    @Schema(description = "心率(次/分钟)")
+    private Integer heartRate;
+
+    @Schema(description = "心率测量时间")
+    private LocalDateTime heartRateMeasureTime;
+}

+ 30 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/PhysicalDataServiceImpl.java

@@ -14,6 +14,8 @@ import work.baiyun.chronicdiseaseapp.service.PhysicalDataService;
 
 import java.util.List;
 import java.util.stream.Collectors;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 @Service
 public class PhysicalDataServiceImpl implements PhysicalDataService {
@@ -47,6 +49,20 @@ public class PhysicalDataServiceImpl implements PhysicalDataService {
                     PhysicalDataResponse r = new PhysicalDataResponse();
                     BeanUtils.copyProperties(record, r);
                     r.setId(record.getId().toString());
+                    // 计算 BMI(动态,不入库),height 单位 cm -> 转换为 m
+                    if (record.getHeight() != null && record.getWeight() != null) {
+                        BigDecimal heightCm = record.getHeight();
+                        BigDecimal weightKg = record.getWeight();
+                        if (heightCm.compareTo(BigDecimal.ZERO) > 0) {
+                            try {
+                                BigDecimal heightM = heightCm.divide(BigDecimal.valueOf(100), 6, RoundingMode.HALF_UP);
+                                BigDecimal bmi = weightKg.divide(heightM.multiply(heightM), 2, RoundingMode.HALF_UP);
+                                r.setBmi(bmi);
+                            } catch (ArithmeticException ignored) {
+                                // fallback: do not set bmi if math errors occur
+                            }
+                        }
+                    }
                     return r;
                 })
                 .collect(Collectors.toList());
@@ -82,6 +98,20 @@ public class PhysicalDataServiceImpl implements PhysicalDataService {
             PhysicalDataResponse resp = new PhysicalDataResponse();
             org.springframework.beans.BeanUtils.copyProperties(r, resp);
             resp.setId(r.getId().toString());
+            // 计算 BMI(动态,不入库)
+            if (r.getHeight() != null && r.getWeight() != null) {
+                BigDecimal heightCm = r.getHeight();
+                BigDecimal weightKg = r.getWeight();
+                if (heightCm.compareTo(BigDecimal.ZERO) > 0) {
+                    try {
+                        BigDecimal heightM = heightCm.divide(BigDecimal.valueOf(100), 6, RoundingMode.HALF_UP);
+                        BigDecimal bmi = weightKg.divide(heightM.multiply(heightM), 2, RoundingMode.HALF_UP);
+                        resp.setBmi(bmi);
+                    } catch (ArithmeticException ignored) {
+                        // ignore and leave bmi null
+                    }
+                }
+            }
             return resp;
         }).collect(java.util.stream.Collectors.toList());