当前系统对重复上传文件有完整的处理机制,分为同一机构重复上传和不同机构重复上传两种场景。
行为: 文件会被重新保存,不会覆盖旧文件
原因:
IdUtil.simpleUUID(){institutionId}/{date}/{uuid}.dcm代码位置: DicomServiceImpl.java:80-82
String fileId = IdUtil.simpleUUID(); // 每次都是新的UUID
String relativePath = institutionId + "/" + LocalDate.now().toString() + "/" + fileId + fileExtension;
结果:
行为: 根据 patient_id 查找,如果存在则更新,不存在则新建
代码位置: DicomServiceImpl.java:428-458
逻辑:
// 通过 patient_id 查询
LambdaQueryWrapper<PatientInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PatientInfo::getPatientId, dicomData.getPatientId());
PatientInfo patientInfo = patientInfoMapper.selectOne(wrapper);
if (patientInfo == null) {
// 新建患者记录
} else {
// 更新患者信息(只更新空字段)
}
结果:
行为: 根据 study_instance_uid 查找,如果存在则更新,不存在则新建
代码位置: DicomServiceImpl.java:468-535
逻辑:
// 通过 study_instance_uid 查询
LambdaQueryWrapper<StudyInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StudyInfo::getStudyInstanceUid, dicomData.getStudyInstanceUid());
StudyInfo studyInfo = studyInfoMapper.selectOne(wrapper);
if (studyInfo == null) {
// 新建检查记录
} else {
// 更新:更新文件路径和上传时间
studyInfo.setDicomFilePath(dicomFilePath);
studyInfo.setUploadTime(LocalDateTime.now());
studyInfo.setUpdateTime(LocalDateTime.now());
studyInfoMapper.updateById(studyInfo);
}
结果:
行为: 在异步解析时,根据 sop_instance_uid 检查,如果存在则跳过
代码位置: DicomAsyncServiceImpl.java:162-170
逻辑:
// 检查是否已存在(通过sop_instance_uid)
LambdaQueryWrapper<SeriesInfo> query = new LambdaQueryWrapper<>();
query.eq(SeriesInfo::getSopInstanceUid, dicomData.getSopInstanceUID());
SeriesInfo existing = seriesInfoMapper.selectOne(query);
if (existing != null) {
log.info("片子已存在,跳过: sopInstanceUid={}", dicomData.getSopInstanceUID());
continue; // 跳过保存
}
结果:
uk_series_sop (series_instance_uid + sop_instance_uid) 保证唯一性行为: 文件保存到不同机构目录
存储路径:
INST_A/2025-12-30/uuid1.dcmINST_B/2025-12-30/uuid2.dcm结果:
行为: 创建不同机构的患者记录
查询条件: patient_id + institution_id
结果:
行为: 创建不同机构的检查记录
查询条件: study_instance_uid + institution_id
重要说明:
study_instance_uid 应该全局唯一institution_id 条件代码位置: DicomServiceImpl.java:471-473
LambdaQueryWrapper<StudyInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StudyInfo::getStudyInstanceUid, dicomData.getStudyInstanceUid());
// ⚠️ 注意:这里没有加 institutionId 条件
StudyInfo studyInfo = studyInfoMapper.selectOne(wrapper);
潜在问题:
study_instance_uid,会视为同一个检查行为: 创建不同机构的序列/实例记录
查询条件: sop_instance_uid 全局查询(无 institution_id)
代码位置: DicomAsyncServiceImpl.java:163-165
LambdaQueryWrapper<SeriesInfo> query = new LambdaQueryWrapper<>();
query.eq(SeriesInfo::getSopInstanceUid, dicomData.getSopInstanceUID());
// ⚠️ 注意:这里也没有加 institutionId 条件
SeriesInfo existing = seriesInfoMapper.selectOne(query);
潜在问题:
sop_instance_uid,后上传的会跳过| 场景 | 会不会报错 | 说明 |
|---|---|---|
| 文件保存 | ❌ 不会 | 生成新文件,不覆盖 |
| 患者信息 | ❌ 不会 | 合并更新 |
| 检查信息 | ❌ 不会 | 更新文件路径 |
| 序列/实例 | ❌ 不会 | 跳过已存在的片子 |
| 数据库约束 | ❌ 不会 | uk_series_sop 允许同一序列多张片子 |
| 场景 | 会不会报错 | 条件 |
|---|---|---|
| 文件保存 | ❌ 不会 | 路径不同,完全独立 |
| 患者信息 | ❌ 不会 | 不同机构的记录 |
| 检查信息 | ⚠️ 可能 | 如果 study_instance_uid 相同 |
| 序列/实例 | ⚠️ 可能 | 如果 sop_instance_uid 相同 |
| 数据库约束 | ⚠️ 可能 | 违反唯一约束 |
位置:
DicomServiceImpl.java:471-473 - Study查询DicomAsyncServiceImpl.java:163-165 - Series查询问题: 只用UID查询,没有加 institution_id 条件
影响:
问题: 同一机构重复上传会保存多份相同的文件
影响:
问题: Study记录更新时,只更新 dicom_file_path,但不检查该路径是否正确
影响:
修改点1: Study查询
// 当前代码
wrapper.eq(StudyInfo::getStudyInstanceUid, dicomData.getStudyInstanceUid());
// 建议修改为
wrapper.eq(StudyInfo::getStudyInstanceUid, dicomData.getStudyInstanceUid());
wrapper.eq(StudyInfo::getInstitutionId, institutionId); // 添加这行
修改点2: Series查询
// 当前代码
query.eq(SeriesInfo::getSopInstanceUid, dicomData.getSopInstanceUID());
// 建议修改为
query.eq(SeriesInfo::getSopInstanceUid, dicomData.getSopInstanceUID());
query.eq(SeriesInfo::getInstitutionId, institutionId); // 添加这行
在上传前计算文件MD5,如果相同文件已存在,则跳过保存:
// 计算上传文件的MD5
String fileMd5 = DigestUtils.md5Hex(file.getInputStream());
// 检查是否已存在相同文件
LambdaQueryWrapper<SeriesInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SeriesInfo::getFileMd5, fileMd5);
SeriesInfo existing = seriesInfoMapper.selectOne(wrapper);
if (existing != null) {
log.info("文件已存在,跳过上传: md5={}, existingPath={}",
fileMd5, existing.getFilePath());
return existing; // 返回已存在的记录
}
定期扫描并清理数据库中没有引用的DICOM文件:
-- 查找僵尸文件(数据库中不存在的文件)
SELECT file_path FROM series_info
WHERE file_path NOT IN (
SELECT CONCAT(dicom_root_path, dicom_file_path, '/', file_id, '.dcm')
FROM study_info
);
✅ 同一机构重复上传: 完全支持,不会报错 ⚠️ 不同机构重复上传: 基本支持,但存在UID冲突风险
institution_id 到查询条件(防止数据混乱)