|
|
@@ -0,0 +1,468 @@
|
|
|
+package com.zskk.pacsonline.modules.report.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.zskk.pacsonline.modules.doctor.entity.Doctors;
|
|
|
+import com.zskk.pacsonline.modules.doctor.mapper.DoctorsMapper;
|
|
|
+import com.zskk.pacsonline.modules.org.entity.SysOrg;
|
|
|
+import com.zskk.pacsonline.modules.org.mapper.SysOrgMapper;
|
|
|
+import com.zskk.pacsonline.modules.report.dto.ReportDetailDTO;
|
|
|
+import com.zskk.pacsonline.modules.report.dto.ReportTraceDTO;
|
|
|
+import com.zskk.pacsonline.modules.report.entity.Report;
|
|
|
+import com.zskk.pacsonline.modules.report.entity.ReportTemp;
|
|
|
+import com.zskk.pacsonline.modules.report.mapper.ReportDetailMapper;
|
|
|
+import com.zskk.pacsonline.modules.report.mapper.ReportMapper;
|
|
|
+import com.zskk.pacsonline.modules.report.mapper.ReportTempMapper;
|
|
|
+import com.zskk.pacsonline.modules.report.service.ReportDetailService;
|
|
|
+import com.zskk.pacsonline.modules.report.vo.ReportDetailQueryVO;
|
|
|
+import com.zskk.pacsonline.security.LoginUser;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.security.core.Authentication;
|
|
|
+import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 报告详情服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @since 2025-12-16
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class ReportDetailServiceImpl implements ReportDetailService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportDetailMapper reportDetailMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportMapper reportMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportTempMapper reportTempMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private SysOrgMapper sysOrgMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DoctorsMapper doctorsMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public ReportDetailDTO getReportDetail(ReportDetailQueryVO queryVO) {
|
|
|
+ String examId = queryVO.getExamId();
|
|
|
+ Integer isRemote = queryVO.getIsRemote();
|
|
|
+ String raId = queryVO.getRaId();
|
|
|
+
|
|
|
+ ReportDetailDTO detail;
|
|
|
+
|
|
|
+ // 1. 根据 raId 判断查询本地报告还是远程报告
|
|
|
+ if (StringUtils.isBlank(raId)) {
|
|
|
+ // 本地报告详情
|
|
|
+ detail = reportDetailMapper.selectLocalReportDetail(examId);
|
|
|
+
|
|
|
+ // 如果报告不存在,自动创建一条报告记录
|
|
|
+ if (detail == null || StringUtils.isBlank(detail.getReportId())) {
|
|
|
+ createReportIfNotExists(examId, detail);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 远程报告详情
|
|
|
+ detail = reportDetailMapper.selectRemoteReportDetail(raId);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (detail == null) {
|
|
|
+ throw new RuntimeException("检查信息不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 补充机构信息(报告标题、副标题、医院简介)
|
|
|
+ supplementInstitutionInfo(detail);
|
|
|
+
|
|
|
+ // 3. TODO: 获取 DICOM 文件路径
|
|
|
+ // supplementDcmPath(detail);
|
|
|
+
|
|
|
+ // 4. 查询医生签名信息(批量查询3个医生)
|
|
|
+ queryDoctorSignatures(detail);
|
|
|
+
|
|
|
+ // 5. 处理医生签名 URL(根据存储类型生成访问路径)
|
|
|
+ processAutographUrls(detail);
|
|
|
+
|
|
|
+ // 6. 查询报告医生所属机构信息
|
|
|
+ queryDoctorInstitution(detail);
|
|
|
+
|
|
|
+ // 7. 设置基础字段
|
|
|
+ detail.setExamId(examId);
|
|
|
+ detail.setIsRemote(isRemote);
|
|
|
+
|
|
|
+ // 8. 查询操作痕迹
|
|
|
+ List<ReportTraceDTO> trace = getReportTrace(detail.getReportId());
|
|
|
+ detail.setTrace(trace);
|
|
|
+
|
|
|
+ // 9. 如果报告未完成,加载草稿内容
|
|
|
+ loadDraftIfNotCompleted(detail);
|
|
|
+
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 如果报告不存在,自动创建
|
|
|
+ * MyBatis-Plus 会自动生成 UUID(实体类配置了 @TableId(type = IdType.ASSIGN_UUID))
|
|
|
+ *
|
|
|
+ * @param examId 检查ID
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void createReportIfNotExists(String examId, ReportDetailDTO detail) {
|
|
|
+ Report report = new Report();
|
|
|
+ report.setExamId(examId);
|
|
|
+ report.setType("1"); // 本地报告
|
|
|
+ report.setCreateTime(new Date());
|
|
|
+
|
|
|
+ // MyBatis-Plus 会自动生成 ID
|
|
|
+ int rows = reportMapper.insert(report);
|
|
|
+ if (rows > 0) {
|
|
|
+ log.info("自动创建报告成功,reportId={}", report.getId());
|
|
|
+ if (detail != null) {
|
|
|
+ detail.setReportId(report.getId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 补充机构信息(报告标题、副标题、医院简介)
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void supplementInstitutionInfo(ReportDetailDTO detail) {
|
|
|
+ if (StringUtils.isBlank(detail.getInstitutionId())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ SysOrg org = sysOrgMapper.selectById(detail.getInstitutionId());
|
|
|
+ if (org != null) {
|
|
|
+ detail.setReportTitle(org.getReportTitle());
|
|
|
+ detail.setReportSubtitle(org.getReportSubtitle());
|
|
|
+ detail.setHrInfo(org.getHrInfo());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询医生签名信息(批量查询3个医生)
|
|
|
+ * 性能优化:使用批量查询代替SQL中的3次LEFT JOIN
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void queryDoctorSignatures(ReportDetailDTO detail) {
|
|
|
+ // 收集需要查询的医生ID
|
|
|
+ List<String> doctorIds = new ArrayList<>();
|
|
|
+ if (StringUtils.isNotBlank(detail.getReportDoctorId())) {
|
|
|
+ doctorIds.add(detail.getReportDoctorId());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(detail.getReviewDoctorId())) {
|
|
|
+ doctorIds.add(detail.getReviewDoctorId());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(detail.getConfirmDoctorId())) {
|
|
|
+ doctorIds.add(detail.getConfirmDoctorId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有医生ID,直接返回
|
|
|
+ if (doctorIds.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询医生信息
|
|
|
+ List<Doctors> doctors = doctorsMapper.selectBatchIds(doctorIds);
|
|
|
+ if (doctors == null || doctors.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将医生列表转换为Map,方便按ID查找
|
|
|
+ Map<String, Doctors> doctorMap = new HashMap<>();
|
|
|
+ for (Doctors doctor : doctors) {
|
|
|
+ doctorMap.put(doctor.getId(), doctor);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充报告医生签名信息
|
|
|
+ if (StringUtils.isNotBlank(detail.getReportDoctorId())) {
|
|
|
+ Doctors reportDoctor = doctorMap.get(detail.getReportDoctorId());
|
|
|
+ if (reportDoctor != null) {
|
|
|
+ detail.setReportUseAutograph(reportDoctor.getIsUseAutograph());
|
|
|
+ detail.setReportAutograph(reportDoctor.getAutograph());
|
|
|
+ detail.setReportAutographType(reportDoctor.getAutographType());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充审核医生签名信息
|
|
|
+ if (StringUtils.isNotBlank(detail.getReviewDoctorId())) {
|
|
|
+ Doctors reviewDoctor = doctorMap.get(detail.getReviewDoctorId());
|
|
|
+ if (reviewDoctor != null) {
|
|
|
+ detail.setReviewUseAutograph(reviewDoctor.getIsUseAutograph());
|
|
|
+ detail.setReviewAutograph(reviewDoctor.getAutograph());
|
|
|
+ detail.setReviewAutographType(reviewDoctor.getAutographType());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充确认医生签名信息
|
|
|
+ if (StringUtils.isNotBlank(detail.getConfirmDoctorId())) {
|
|
|
+ Doctors confirmDoctor = doctorMap.get(detail.getConfirmDoctorId());
|
|
|
+ if (confirmDoctor != null) {
|
|
|
+ detail.setConfirmUseAutograph(confirmDoctor.getIsUseAutograph());
|
|
|
+ detail.setConfirmAutograph(confirmDoctor.getAutograph());
|
|
|
+ detail.setConfirmAutographType(confirmDoctor.getAutographType());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * TODO: 获取 DICOM 文件路径
|
|
|
+ *
|
|
|
+ * 实现逻辑:
|
|
|
+ * 1. 根据 study_id 查询 dcm_path 表
|
|
|
+ * 2. 检查 effective_date 是否过期
|
|
|
+ * 3. 根据 dcm_type 生成访问 URL
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ // private void supplementDcmPath(ReportDetailDTO detail) {
|
|
|
+ // // TODO: 实现 DICOM 路径查询和 URL 生成
|
|
|
+ // }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理医生签名 URL
|
|
|
+ * 根据存储类型(本地/云存储)生成对应的访问路径
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void processAutographUrls(ReportDetailDTO detail) {
|
|
|
+ // 报告医生签名
|
|
|
+ detail.setReportAutograph(
|
|
|
+ generateAutographUrl(
|
|
|
+ detail.getReportUseAutograph(),
|
|
|
+ detail.getReportAutograph(),
|
|
|
+ detail.getReportAutographType()
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ // 审核医生签名
|
|
|
+ detail.setReviewAutograph(
|
|
|
+ generateAutographUrl(
|
|
|
+ detail.getReviewUseAutograph(),
|
|
|
+ detail.getReviewAutograph(),
|
|
|
+ detail.getReviewAutographType()
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ // 确认医生签名
|
|
|
+ detail.setConfirmAutograph(
|
|
|
+ generateAutographUrl(
|
|
|
+ detail.getConfirmUseAutograph(),
|
|
|
+ detail.getConfirmAutograph(),
|
|
|
+ detail.getConfirmAutographType()
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成签名访问 URL
|
|
|
+ *
|
|
|
+ * @param useAutograph 是否使用签名:0=否,1=是
|
|
|
+ * @param autograph 签名文件路径
|
|
|
+ * @param autographType 存储类型:1=本地,2=云存储
|
|
|
+ * @return 签名 URL,如果不使用签名或路径为空,返回空字符串
|
|
|
+ */
|
|
|
+ private String generateAutographUrl(Integer useAutograph, String autograph, Integer autographType) {
|
|
|
+ // 如果不使用签名,返回空
|
|
|
+ if (useAutograph == null || useAutograph != 1) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果签名路径为空,返回空
|
|
|
+ if (StringUtils.isBlank(autograph)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据存储类型生成 URL
|
|
|
+ if (autographType != null && autographType == 2) {
|
|
|
+ // TODO: 云存储,调用云存储 SDK 生成临时访问 URL
|
|
|
+ // return cloudStorageService.generateSignedUrl(autograph);
|
|
|
+ return autograph; // 暂时直接返回路径
|
|
|
+ } else {
|
|
|
+ // 本地存储,直接返回路径
|
|
|
+ return autograph;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询报告医生所属机构信息
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void queryDoctorInstitution(ReportDetailDTO detail) {
|
|
|
+ if (StringUtils.isBlank(detail.getReportDoctorId())) {
|
|
|
+ detail.setInstitution("");
|
|
|
+ detail.setIntroduce("");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询医生信息(包含机构ID和简介)
|
|
|
+ Doctors doctor = doctorsMapper.selectById(detail.getReportDoctorId());
|
|
|
+ if (doctor == null) {
|
|
|
+ detail.setInstitution("");
|
|
|
+ detail.setIntroduce("");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置医生简介
|
|
|
+ detail.setIntroduce(StringUtils.defaultString(doctor.getIntroduce(), ""));
|
|
|
+
|
|
|
+ // 查询机构名称
|
|
|
+ if (StringUtils.isNotBlank(doctor.getOid())) {
|
|
|
+ SysOrg org = sysOrgMapper.selectById(doctor.getOid());
|
|
|
+ if (org != null) {
|
|
|
+ detail.setInstitution(org.getName());
|
|
|
+ } else {
|
|
|
+ detail.setInstitution("");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ detail.setInstitution("");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取报告操作痕迹
|
|
|
+ *
|
|
|
+ * @param reportId 报告ID
|
|
|
+ * @return 操作痕迹列表
|
|
|
+ */
|
|
|
+ private List<ReportTraceDTO> getReportTrace(String reportId) {
|
|
|
+ if (StringUtils.isBlank(reportId)) {
|
|
|
+ return List.of();
|
|
|
+ }
|
|
|
+
|
|
|
+ return reportDetailMapper.selectReportTrace(reportId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 如果报告未完成,加载草稿内容
|
|
|
+ * 当报告处于完成状态(exam_status=9 或 report_status=9)时,不加载草稿
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ */
|
|
|
+ private void loadDraftIfNotCompleted(ReportDetailDTO detail) {
|
|
|
+ // 检查报告是否已完成
|
|
|
+ if (isReportCompleted(detail)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前登录用户ID
|
|
|
+ String currentUserId = getCurrentUserId();
|
|
|
+ if (StringUtils.isBlank(currentUserId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询草稿
|
|
|
+ ReportTemp draft = queryDraft(detail.getReportId(), currentUserId);
|
|
|
+ if (draft != null) {
|
|
|
+ // 用草稿内容覆盖数据库内容
|
|
|
+ if (StringUtils.isNotBlank(draft.getImpression())) {
|
|
|
+ detail.setImpression(draft.getImpression());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(draft.getDescription())) {
|
|
|
+ detail.setDescription(draft.getDescription());
|
|
|
+ }
|
|
|
+ log.debug("加载草稿成功,reportId={}, userId={}", detail.getReportId(), currentUserId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断报告是否已完成
|
|
|
+ *
|
|
|
+ * @param detail 报告详情
|
|
|
+ * @return true=已完成,false=未完成
|
|
|
+ */
|
|
|
+ private boolean isReportCompleted(ReportDetailDTO detail) {
|
|
|
+ // exam_status = 9 表示检查已完成
|
|
|
+ if (detail.getExamStatus() != null && detail.getExamStatus() == 9) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // report_status = 9 表示报告已完成(远程报告)
|
|
|
+ if (detail.getReportStatus() != null && detail.getReportStatus() == 9) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前登录医生ID
|
|
|
+ * 从 Spring Security 上下文获取当前登录用户,然后查询对应的医生ID
|
|
|
+ *
|
|
|
+ * @return 医生ID,如果获取失败返回null
|
|
|
+ */
|
|
|
+ private String getCurrentUserId() {
|
|
|
+ try {
|
|
|
+ // 1. 从 Spring Security 获取当前登录用户
|
|
|
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
|
+ if (authentication == null || authentication.getPrincipal() == null) {
|
|
|
+ log.warn("未找到登录用户信息");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取 LoginUser 对象
|
|
|
+ Object principal = authentication.getPrincipal();
|
|
|
+ if (!(principal instanceof LoginUser)) {
|
|
|
+ log.warn("Principal 不是 LoginUser 类型: {}", principal.getClass().getName());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ LoginUser loginUser = (LoginUser) principal;
|
|
|
+ if (loginUser.getUser() == null || StringUtils.isBlank(loginUser.getUser().getId())) {
|
|
|
+ log.warn("LoginUser 中未找到用户信息");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String sysUserId = loginUser.getUser().getId();
|
|
|
+ log.debug("当前登录系统用户ID: {}", sysUserId);
|
|
|
+
|
|
|
+ // 3. 根据系统用户ID查询医生ID(doctors 表的 uid 字段关联 sys_user 的 id)
|
|
|
+ LambdaQueryWrapper<Doctors> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(Doctors::getUid, sysUserId)
|
|
|
+ .last("LIMIT 1");
|
|
|
+
|
|
|
+ Doctors doctor = doctorsMapper.selectOne(wrapper);
|
|
|
+ if (doctor == null) {
|
|
|
+ log.warn("未找到系统用户对应的医生信息,sysUserId={}", sysUserId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("当前登录医生ID: {}", doctor.getId());
|
|
|
+ return doctor.getId();
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取当前医生ID失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询草稿
|
|
|
+ *
|
|
|
+ * @param reportId 报告ID
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return 草稿内容
|
|
|
+ */
|
|
|
+ private ReportTemp queryDraft(String reportId, String userId) {
|
|
|
+ if (StringUtils.isBlank(reportId) || StringUtils.isBlank(userId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<ReportTemp> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(ReportTemp::getReportId, reportId)
|
|
|
+ .eq(ReportTemp::getDoctorId, userId)
|
|
|
+ .orderByDesc(ReportTemp::getUpdateTime)
|
|
|
+ .last("LIMIT 1");
|
|
|
+
|
|
|
+ return reportTempMapper.selectOne(wrapper);
|
|
|
+ }
|
|
|
+}
|