|
@@ -0,0 +1,141 @@
|
|
|
|
|
+# 查询绑定患者健康数据设计
|
|
|
|
|
+
|
|
|
|
|
+## 概述
|
|
|
|
|
+当患者与医生/家属建立绑定关系后,允许被绑定的医生或家属查询该患者的健康数据(血糖、血压、心率、体征等)。本设计描述目标、接口、权限校验、实现要点、测试与文档规范,并参照项目已经存在的经验文档(如 ID 精度、Swagger 泛型文档化)。
|
|
|
|
|
+
|
|
|
|
|
+## 背景和目标
|
|
|
|
|
+- 背景:已有用户绑定(患者 ↔ 医生/家属)功能,且患者在系统内可记录多类健康数据。为了满足医生/家属随访与监护需求,需要允许绑定方访问患者数据。
|
|
|
|
|
+- 目标:在保证安全、最小权限原则、并兼顾 API 统一性的前提下实现“绑定方查询患者健康数据”接口。
|
|
|
|
|
+
|
|
|
|
|
+## 需求与约束
|
|
|
|
|
+1. 认证:接口需走已有的Token认证(AuthInterceptor),并依赖 request attribute 中的 `currentUserId`。
|
|
|
|
|
+2. 绑定检查:接口必须校验当前请求用户(boundUser)与目标患者(patientUserId)之间是否存在有效绑定关系(`UserBindingService.checkUserBinding`)。
|
|
|
|
|
+3. 权限粒度:区分绑定类型(DOCTOR / FAMILY),医生和家属的可见字段或时间范围可不同(设计建议见下)。
|
|
|
|
|
+4. 日志审计:所有第三方查询患者数据的调用要记录访问日志,便于审计。
|
|
|
|
|
+5. API 兼容:使用与现有分页查询一致的参数和返回格式(`R<T>` + `Page<T>` VO)。
|
|
|
|
|
+6. 文档与前端:参照 `docs/Swagger泛型返回类型字段信息不显示问题解决方案.md`,在 Controller 层使用 `@ApiResponse` 与 `@Schema(implementation = ...)` 标注返回类型;参照 `docs/前端ID精度丢失问题解决方案.md` 将 id 字段返回为 string。
|
|
|
|
|
+
|
|
|
|
|
+## 设计要点(高层)
|
|
|
|
|
+1. 在每类健康数据 Controller(`BloodGlucoseDataController`, `BloodPressureDataController`, `HeartRateDataController`, `PhysicalDataController` 等)增加接口 `list-by-bound-user`:
|
|
|
|
|
+ - HTTP 方法:POST(与现有 list 同形式),路径示例:`/blood-glucose/list-by-bound-user`。
|
|
|
|
|
+ - 参数:`patientUserId`(Long)、`bindingType`(String, 可选)、`BaseQueryRequest`(分页、时间范围)
|
|
|
|
|
+ - 身份鉴权:不允许在 body 内传入 boundUserId,使用 `AuthInterceptor` 提供的 `currentUserId` 来判断请求人的身份。
|
|
|
|
|
+ - 核心逻辑:先 `userBindingService.checkUserBinding(patientUserId, currentUserId)`,若不存在返回 `R.fail(ErrorCode.DATA_ACCESS_DENIED)`;否则继续查询并返回数据。
|
|
|
|
|
+
|
|
|
|
|
+2. Service 层提供 `listDataByPatient(patientUserId, BaseQueryRequest)` 等方法,返回 `Page<TResponse>` 供 Controller 组装对应 PageResponse VO。
|
|
|
|
|
+
|
|
|
|
|
+3. 权限策略(建议):
|
|
|
|
|
+ - DOCTOR:默认具有查询所有字段的权限;可以查看所有历史数据(受分页限制)。
|
|
|
|
|
+ - FAMILY:只允许查询近 N 天(例如 365 天内)或限制敏感字段(如“医生诊断备注”等),并在返回字段中做适当脱敏/限制。绑定关系可在 `CreateUserBindingRequest` 中声明。
|
|
|
|
|
+
|
|
|
|
|
+4. 审计日志:
|
|
|
|
|
+ - 在 Controller 的 `list-by-bound-user` 接口中记录 `boundUserId`, `patientUserId`, `queryType`, `startTime`, `endTime`,写入到 regular logs 或独立审计表(参考 `docs/DevRule/06-日志和错误处理规范.md`)。
|
|
|
|
|
+
|
|
|
|
|
+## API 设计示例
|
|
|
|
|
+
|
|
|
|
|
+示例 - 血糖数据:
|
|
|
|
|
+
|
|
|
|
|
+Controller 片段(示例,仅说明用法)
|
|
|
|
|
+
|
|
|
|
|
+````java
|
|
|
|
|
+@Operation(summary = "医生/家属分页查询患者血糖数据", description = "绑定方查询患者血糖测量记录(需有绑定关系)")
|
|
|
|
|
+@ApiResponses(value = {
|
|
|
|
|
+ @ApiResponse(responseCode = "200", description = "成功查询血糖数据列表",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.BloodGlucoseDataPageResponse.class))),
|
|
|
|
|
+ @ApiResponse(responseCode = "403", description = "无权限访问",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
|
|
+})
|
|
|
|
|
+@PostMapping(path = "/list-by-bound-user", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
|
|
+public R<?> listByBoundUser(Long patientUserId, String bindingType, @RequestBody BaseQueryRequest req, HttpServletRequest request) {
|
|
|
|
|
+ Long boundUserId = (Long) request.getAttribute("currentUserId");
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 校验参数
|
|
|
|
|
+ if (patientUserId == null) {
|
|
|
|
|
+ return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), ErrorCode.PARAMETER_ERROR.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 校验绑定关系
|
|
|
|
|
+ CheckUserBindingRequest checkReq = new CheckUserBindingRequest();
|
|
|
|
|
+ checkReq.setPatientUserId(patientUserId);
|
|
|
|
|
+ checkReq.setBoundUserId(boundUserId);
|
|
|
|
|
+ CheckUserBindingResponse checkResp = userBindingService.checkUserBinding(checkReq);
|
|
|
|
|
+ if (!checkResp.getExists()) {
|
|
|
|
|
+ return R.fail(ErrorCode.DATA_ACCESS_DENIED.getCode(), "当前用户与目标患者未建立绑定关系");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 依据权限决定查询逻辑(可在 service 层统一处理)
|
|
|
|
|
+ Page<BloodGlucoseDataResponse> page = service.listBloodGlucoseDataByPatient(patientUserId, bindingType, req);
|
|
|
|
|
+ // 下面同 list 接口组装 PageResponse,返回
|
|
|
|
|
+}
|
|
|
|
|
+````
|
|
|
|
|
+
|
|
|
|
|
+Service 层新增方法签名示例:
|
|
|
|
|
+
|
|
|
|
|
+````java
|
|
|
|
|
+Page<BloodGlucoseDataResponse> listBloodGlucoseDataByPatient(Long patientUserId, String bindingType, BaseQueryRequest request);
|
|
|
|
|
+````
|
|
|
|
|
+
|
|
|
|
|
+实现要点:
|
|
|
|
|
+- 在 mapper 查询条件中替换 `eq(BloodGlucoseData::getUserId, userId)` 为 patientUserId。
|
|
|
|
|
+- 对 FAMILY 限制时间范围(例如:request.setStartTime >= now - 365 天);或在 mapper 层追加条件。
|
|
|
|
|
+
|
|
|
|
|
+## Swagger 与 API 文档
|
|
|
|
|
+- 统一使用 `@ApiResponse` + `@Content` + `@Schema(implementation = ...)` 来避免泛型类型在文档中缺失(参考 docs/Swagger泛型返回类型...)。
|
|
|
|
|
+- 所有 VO 的 id 字段在响应层转换成 `String`(参见 docs/前端ID精度...)。
|
|
|
|
|
+
|
|
|
|
|
+示例:
|
|
|
|
|
+
|
|
|
|
|
+````java
|
|
|
|
|
+@ApiResponse(responseCode = "200", description = "查询成功",
|
|
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
|
|
+ schema = @Schema(implementation = work.baiyun.chronicdiseaseapp.model.vo.BloodGlucoseDataPageResponse.class)))
|
|
|
|
|
+````
|
|
|
|
|
+
|
|
|
|
|
+## 权限/业务细节与差异化策略
|
|
|
|
|
+1. 当 `bindingType = DOCTOR`:
|
|
|
|
|
+ - 默认开放全部字段及历史记录(分页受 request 限制)。
|
|
|
|
|
+2. 当 `bindingType = FAMILY`:
|
|
|
|
|
+ - 可限定时间窗口(例如 365 天)和/或禁止返回某些敏感字段(如直接的医疗诊断、医生备注),也可在 VO 中对字段进行脱敏。
|
|
|
|
|
+3. 若 `bindingType` 为空:由 `userBindingService.checkUserBinding` 返回的绑定类型决定权限。
|
|
|
|
|
+
|
|
|
|
|
+实现小技巧:在 `UserBindingService` 中返回绑定类型(CheckUserBindingResponse 已有),Controller/Service 使用该返回值作为权限判断依据。
|
|
|
|
|
+
|
|
|
|
|
+## 日志 & 审计
|
|
|
|
|
+1. 写入标准访问日志,格式包括 `boundUserId`, `patientUserId`, `bindingType`, `operation`, `queryStart`, `queryEnd`, `resultCount`。
|
|
|
|
|
+2. 如需合规审计,可在数据库中建立审计表 `patient_data_access_log` 记录详细调用历史;字段示例:id、access_time、accessor_id、patient_id、data_type、start_time、end_time、ip_address。
|
|
|
|
|
+
|
|
|
|
|
+## 测试建议
|
|
|
|
|
+1. 单元测试:模拟 UserBindingService 返回不同绑定关系,验证 `listByBoundUser` 返回或拒绝的逻辑(正常与未绑定)。
|
|
|
|
|
+2. 集成测试:启动应用(或测试环境),分别用 doctor / family 的 token 发起查询请求,校验返回结果字段及分页。注意验证 ID 字段为字符串。
|
|
|
|
|
+3. 性能测试:对长时间范围查询与频繁查询场景做压测,评估 DB 索引是否充分;若发现慢查询,建议加索引(例如测量时间 idx)或靠缓存(例如热点患者数据)优化。
|
|
|
|
|
+
|
|
|
|
|
+## 兼容性与前端要求
|
|
|
|
|
+1. 前端应使用 token 登录并把 token 放在 `Authorization: Bearer <token>` 中。AuthInterceptor 将解析并注入 `currentUserId`。
|
|
|
|
|
+2. 前端需要了解 `id` 字段为字符串,不要当作 Number 处理(参考 docs/前端ID精度...)。
|
|
|
|
|
+3. 文档中明确标注字段是否对家属脱敏或受时间窗口限制。
|
|
|
|
|
+
|
|
|
|
|
+## 迁移/变更管理
|
|
|
|
|
+- 该功能应由小步提交:先实现后端接口和测试,再在 Swagger 文档中公开,最后通知前端(以便正确处理 id 字符串与权限错误)。
|
|
|
|
|
+
|
|
|
|
|
+## 开发任务清单
|
|
|
|
|
+1. Controller:在 `BloodGlucoseDataController`, `BloodPressureDataController`, `HeartRateDataController`, `PhysicalDataController` 等实现 `list-by-bound-user`。
|
|
|
|
|
+2. Service:添加 `listByPatient` 变体并对 bindingType 做权限过滤。
|
|
|
|
|
+3. Mapper:如必要,添加 `AND user_id = #{patientUserId}` 的查询条件。
|
|
|
|
|
+4. Unit / Integration Tests:覆盖权限校验、数据返回与分页。
|
|
|
|
|
+5. 日志 & 审计:实现访问日志或可选审计表。
|
|
|
|
|
+
|
|
|
|
|
+## 参考
|
|
|
|
|
+- docs/前端ID精度丢失问题解决方案.md
|
|
|
|
|
+- docs/Swagger泛型返回类型字段信息不显示问题解决方案.md
|
|
|
|
|
+- docs/DevRule/03-API设计规范.md
|
|
|
|
|
+- UserBindingController.java / UserBindingService.java
|
|
|
|
|
+
|
|
|
|
|
+## 责任人
|
|
|
|
|
+- 设计:后端开发团队
|
|
|
|
|
+- 实现:后端 API 开发者
|
|
|
|
|
+- 联调:后端 + 前端
|
|
|
|
|
+
|
|
|
|
|
+以上为实现“绑定方查询患者健康数据”功能的设计文档草案,若需要我可以继续生成 Controller / Service 的代码模版以及对应的测试用例。
|
|
|
|
|
+
|