# 报告详情接口实现说明 ## 接口信息 **接口路径**: `POST /api/report/details` **功能说明**: 获取报告详细信息,用于报告书写页面。支持本地报告和远程报告两种模式。 --- ## 实现逻辑 ### 1. 参数验证 - `examId`: 检查ID(必填) - `isRemote`: 报告类型,1=远程报告,2=本地报告(必填) - `raId`: 远程申请ID(远程报告时必填) ### 2. 查询报告基础信息(SQL实现) #### 本地报告查询 ```sql SELECT -- 检查信息(exams表) e.id AS exam_id, e.patient_num, e.name, e.sex, e.age, e.phone, e.exam_class, e.exam_datetime, e.exam_status, e.exam_project, -- 登记信息(register表) reg.body_part, reg.exam_sub_class, -- 报告信息(report表,type='1'表示本地报告) r.id AS report_id, r.description, r.impression, r.diagnose, r.report_result, r.qr_code, r.hr_status, -- 医生信息(只查询医生ID和姓名,签名信息单独批量查询) r.report_doctor_id, r.report_doctor_name, r.report_datetime, r.review_doctor_id, r.review_doctor_name, r.review_datetime, r.confirm_doctor_id, r.confirm_doctor_name, r.confirm_datetime FROM exams e LEFT JOIN register reg ON reg.exam_id = e.id LEFT JOIN report r ON e.id = r.exam_id AND r.type = '1' WHERE e.id = #{examId} ``` **性能优化说明**:移除了3次doctors表LEFT JOIN,改为在Service层批量查询医生签名信息,降低SQL复杂度。 #### 远程报告查询 ```sql SELECT -- 远程申请信息(remote_application表) ra.exam_id, ra.patient_num, ra.name, ra.sex, ra.age, ra.report_status, ra.local_institution_name, ra.req_doctor_name, -- 报告信息(通过 remote_application_id 关联) r.id AS report_id, r.description, r.impression, r.report_result, -- 医生信息(只查询医生ID和姓名,签名信息单独批量查询) r.report_doctor_id, r.report_doctor_name, r.report_datetime, r.review_doctor_id, r.review_doctor_name, r.review_datetime, r.confirm_doctor_id, r.confirm_doctor_name, r.confirm_datetime FROM remote_application ra LEFT JOIN report r ON r.remote_application_id = ra.id WHERE ra.id = #{raId} ``` **性能优化说明**:同本地报告,移除了3次doctors表LEFT JOIN。 ### 3. 自动创建报告记录 如果本地报告不存在(report_id 为空),自动创建一条报告记录: ```java Report report = new Report(); report.setExamId(examId); report.setType("1"); // 本地报告 report.setCreateTime(new Date()); // MyBatis-Plus 会自动生成 UUID(实体类配置了 @TableId(type = IdType.ASSIGN_UUID)) reportMapper.insert(report); ``` ### 4. 补充机构信息 根据 `institution_id` 查询机构的报告标题、副标题、医院简介: ```java SysOrg org = sysOrgMapper.selectById(institutionId); detail.setReportTitle(org.getReportTitle()); detail.setReportSubtitle(org.getReportSubtitle()); detail.setHrInfo(org.getHrInfo()); ``` ### 5. TODO: 获取 DICOM 文件路径 ```java // TODO: 实现逻辑 // 1. 根据 study_id 查询 dcm_path 表 // 2. 检查 effective_date 是否过期 // 3. 根据 dcm_type 生成访问 URL // - dcm_type=1: 本地路径,直接返回 // - dcm_type=2/4: 云存储,调用云存储SDK生成临时访问URL ``` ### 6. 处理医生签名URL 根据每个医生的签名配置生成访问URL: ```java // 判断逻辑: // 1. 是否启用签名?(is_use_autograph == 1) // 2. 签名文件是否存在?(autograph != null) // 3. 根据存储类型生成URL // - autograph_type=1: 本地存储,直接返回路径 // - autograph_type=2: 云存储,生成临时访问URL if (useAutograph == 1 && StringUtils.isNotBlank(autograph)) { if (autographType == 2) { // TODO: 调用云存储SDK生成签名URL url = cloudStorageService.generateSignedUrl(autograph); } else { url = autograph; // 本地路径 } } else { url = ""; // 不使用签名 } ``` ### 7. 查询医生签名信息(批量查询优化) **性能优化**: 不在SQL中LEFT JOIN 3次doctors表,改为批量查询: ```java // 收集需要查询的医生ID(报告医生、审核医生、确认医生) List doctorIds = new ArrayList<>(); if (reportDoctorId != null) doctorIds.add(reportDoctorId); if (reviewDoctorId != null) doctorIds.add(reviewDoctorId); if (confirmDoctorId != null) doctorIds.add(confirmDoctorId); // 批量查询医生信息(1次数据库查询) List doctors = doctorsMapper.selectBatchIds(doctorIds); // 转换为Map,方便查找 Map doctorMap = doctors.stream() .collect(Collectors.toMap(Doctors::getId, d -> d)); // 填充各医生的签名信息 detail.setReportUseAutograph(doctorMap.get(reportDoctorId).getIsUseAutograph()); detail.setReportAutograph(doctorMap.get(reportDoctorId).getAutograph()); // ... 审核医生和确认医生同理 ``` **优化效果**: - 原方案:SQL中LEFT JOIN 3次doctors表(1次主查询 = 1次DB访问) - 新方案:主查询 + 批量查询医生(1次主查询 + 1次批量查询 = 2次DB访问) - 对于2000万+数据的exams表,减少JOIN复杂度,提高查询稳定性 ### 8. 查询医生所属机构 ```java // 查询报告医生的机构信息 if (StringUtils.isNotBlank(reportDoctorId)) { Doctors doctor = doctorsMapper.selectById(reportDoctorId); detail.setIntroduce(doctor.getIntroduce()); SysOrg org = sysOrgMapper.selectById(doctor.getOid()); detail.setInstitution(org.getName()); } ``` ### 9. 查询操作痕迹 从 `report_record` 表查询报告的所有修改历史: ```sql SELECT rr.description, rr.impression, rr.report_result, rr.create_time AS handle_time, d.realname, rr.type FROM report_record rr LEFT JOIN doctors d ON rr.doctor_id = d.id WHERE rr.report_id = #{reportId} ORDER BY rr.create_time ASC ``` ### 10. 加载草稿内容 如果报告未完成(exam_status != 9 且 report_status != 9),从 `report_temp` 表加载当前医生的草稿: ```java // 1. 判断报告是否已完成 if (examStatus == 9 || reportStatus == 9) { return; // 已完成,不加载草稿 } // 2. 获取当前登录医生ID // 从 Spring Security 上下文获取当前用户,然后查询医生ID Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); String sysUserId = loginUser.getUser().getId(); // 根据系统用户ID查询医生ID(doctors.uid = sys_user.id) Doctors doctor = doctorsMapper.selectOne( new LambdaQueryWrapper() .eq(Doctors::getUid, sysUserId) .last("LIMIT 1") ); String currentDoctorId = doctor.getId(); // 3. 查询草稿 ReportTemp draft = reportTempMapper.selectOne( new LambdaQueryWrapper() .eq(ReportTemp::getReportId, reportId) .eq(ReportTemp::getDoctorId, currentDoctorId) .orderByDesc(ReportTemp::getUpdateTime) .last("LIMIT 1") ); // 4. 用草稿覆盖数据库内容 if (draft != null) { detail.setImpression(draft.getImpression()); detail.setDescription(draft.getDescription()); } ``` --- ## 数据表说明 | 表名 | 用途 | |------|------| | `exams` | 检查信息(患者、检查项目、状态等) | | `register` | 登记信息(检查部位、检查子类等) | | `report` | 报告信息(印象、描述、医生、时间等) | | `remote_application` | 远程申请信息 | | `doctors` | 医生信息(姓名、签名、所属机构等) | | `sys_org` | 机构信息(报告标题、副标题等) | | `report_record` | 报告操作记录(历史痕迹) | | `report_temp` | 报告草稿(暂存内容) | | `dcm_path` | DICOM文件路径(TODO) | --- ## 请求示例 ### 本地报告 ```json POST /api/report/details { "examId": "exam-uuid-xxx", "isRemote": 2, "raId": "" } ``` ### 远程报告 ```json POST /api/report/details { "examId": "exam-uuid-xxx", "isRemote": 1, "raId": "ra-uuid-xxx" } ``` --- ## 返回数据示例 ```json { "code": 200, "message": "查询成功", "data": { // 检查基础信息 "examId": "exam-uuid-xxx", "isRemote": 2, "name": "张三", "sex": "男", "age": "45", "phone": "138****5678", "examClass": "CT", "examDatetime": "20251216103000", // 报告信息 "reportId": "report-uuid-xxx", "description": "影像所见内容...", "impression": "诊断印象内容...", "reportResult": "1", // 医生信息 "reportDoctorId": "doc-001", "reportDoctorName": "李医生", "reportDatetime": "2025-12-16 10:30:00", "reviewDoctorId": "doc-002", "reviewDoctorName": "王医生", "reviewDatetime": "2025-12-16 11:00:00", // 医生签名 "reportUseAutograph": 1, "reportAutograph": "https://cloud.com/sign/doc001.png?sign=xxx", "reviewUseAutograph": 0, "reviewAutograph": "", // 机构信息 "institutionId": "org-001", "institution": "北京协和医院", "introduce": "影像科主任医师,从事影像诊断30年", "reportTitle": "医学影像检查报告", "reportSubtitle": "北京协和医院放射科", // DICOM信息 "studyId": "study-uuid-xxx&node_type=1", "dcmPath": "", // TODO "dcmType": null, // TODO // 操作痕迹 "trace": [ { "description": "初步描述...", "impression": "初步印象...", "reportResult": "1", "handleTime": "2025-12-16 10:30:00", "realname": "李医生", "type": 1 }, { "description": "修改后描述...", "impression": "修改后印象...", "reportResult": "1", "handleTime": "2025-12-16 11:00:00", "realname": "王医生", "type": 2 } ] } } ``` --- ## 与原PHP项目的差异 | 功能点 | PHP项目 | Java项目 | |--------|---------|----------| | **草稿存储** | Redis缓存 | 数据库表 `report_temp` | | **医生签名查询** | SQL中LEFT JOIN 3次doctors表 | Service层批量查询(`selectBatchIds`) | | **性能优化** | SQL复杂JOIN | 拆分为主查询 + 批量查询,适合大表(2000万+) | | **DICOM路径** | 查询 dcm_path + 生成URL | TODO(待实现) | | **云存储URL** | uploadToCloud类 | TODO(待实现) | | **事务处理** | 无 | @Transactional注解 | | **ID生成** | 手动调用UUID工具类 | MyBatis-Plus自动生成 | --- ## 待实现功能(TODO) 1. **DICOM文件路径获取** - 查询 `dcm_path` 表 - 检查有效期 - 根据存储类型生成访问URL 2. **云存储签名URL生成** - 集成云存储SDK - 实现临时访问URL生成(12小时有效期) --- ## 文件清单 ### 新增文件 - `ReportDetailQueryVO.java` - 查询参数VO - `ReportDetailDTO.java` - 返回结果DTO - `ReportTraceDTO.java` - 操作痕迹DTO - `ReportTemp.java` - 报告草稿实体 - `ReportRecord.java` - 报告记录实体 - `ReportDetailMapper.java` - Mapper接口 - `ReportDetailMapper.xml` - SQL映射文件 - `ReportTempMapper.java` - 草稿Mapper - `ReportDetailService.java` - 服务接口 - `ReportDetailServiceImpl.java` - 服务实现 ### 修改文件 - `ReportController.java` - 添加 `/details` 接口 --- ## 测试要点 1. ✅ 本地报告查询(有报告记录) 2. ✅ 本地报告查询(无报告记录,自动创建) 3. ✅ 远程报告查询 4. ✅ 草稿加载(报告未完成时) 5. ✅ 草稿不加载(报告已完成时) 6. ✅ 操作痕迹查询 7. ✅ 医生签名处理(启用/未启用) 8. ⏳ DICOM路径获取(待实现) 9. ⏳ 云存储URL生成(待实现)