批量上传功能实现总结.md 8.7 KB

影像批量上传功能实现总结

📋 功能概述

实现了支持文件夹、ZIP压缩包的高级批量上传功能,具备以下特性:

  1. ✅ 支持单个文件上传
  2. ✅ 支持多个文件上传
  3. ✅ 支持文件夹上传
  4. ✅ 支持ZIP压缩包上传
  5. ✅ 自动识别并按study_instance_uid分组
  6. ✅ 使用study_instance_uid作为存储目录
  7. ✅ 支持多study的同一批上传
  8. ✅ 使用batch_id和parent_study_id关联同一批上传的不同study

📁 文件存储结构变化

旧结构

~/qconline/dicom/{institutionId}/{date}/{fileId}.dcm

新结构

~/qconline/dicom/{institutionId}/{study_instance_uid}/{fileId}.dcm

优势: 同一个检查的所有文件都在同一个目录下,便于管理和访问


🗄️ 数据库变化

新增字段

表: study_info

-- 添加批次ID字段
ALTER TABLE `study_info`
ADD COLUMN `batch_id` VARCHAR(50) NULL COMMENT '批次ID:同一批上传的影像共享同一个batch_id'
AFTER `parent_study_id`;

-- 添加索引
ALTER TABLE `study_info`
ADD INDEX `idx_batch_id` (`batch_id`);

字段说明:

  • batch_id: 批次ID,同一批上传的所有study共享同一个batch_id
  • parent_study_id: 父检查ID,第一个study为null,其他study指向第一个study

关联关系:

批次1: batch_id = "BATCH_1234567890"
  ├─ Study A (主study, parent_study_id = NULL)
  ├─ Study B (子study, parent_study_id = Study A的studyId)
  └─ Study C (子study, parent_study_id = Study A的studyId)

🔧 后端实现

1. 新增类文件

ArchiveUtil.java

路径: /src/main/java/com/zskk/qconline/modules/dicom/util/ArchiveUtil.java

功能:

  • 判断文件是否为压缩包(zip)
  • 解压ZIP文件到临时目录
  • 递归查找DICOM文件
  • 创建和清理临时目录

BatchUploadVO.java

路径: /src/main/java/com/zskk/qconline/modules/dicom/vo/BatchUploadVO.java

功能:

  • 批量上传结果封装
  • 包含批次ID、文件总数、study数、study列表等信息

2. 修改的类文件

DicomService.java

路径: /src/main/java/com/zskk/qconline/modules/dicom/service/DicomService.java

新增方法:

BatchUploadVO batchUploadAdvanced(MultipartFile[] files, String institutionId);

DicomServiceImpl.java

路径: /src/main/java/com/zskk/qconline/modules/dicom/service/impl/DicomServiceImpl.java

新增方法:

  • batchUploadAdvanced(): 高级批量上传主方法
  • groupFilesByStudyInstanceUid(): 按study_instance_uid分组文件
  • saveStudyFiles(): 保存文件到study_instance_uid目录

核心逻辑:

  1. 识别上传的文件类型(普通文件或压缩包)
  2. 如果是压缩包,先解压到临时目录
  3. 解析所有DICOM文件的study_instance_uid
  4. 按study_instance_uid分组
  5. 为每个study创建StudyInfo记录
  6. 第一个study作为主study(parent_study_id=null)
  7. 其他study的parent_study_id指向主study
  8. 所有study共享同一个batch_id

StudyInfo.java

路径: /src/main/java/com/zskk/qconline/modules/entity/StudyInfo.java

新增字段:

@TableField("batch_id")
private String batchId;

DicomController.java

路径: /src/main/java/com/zskk/qconline/modules/dicom/controller/DicomController.java

新增接口:

POST /api/dicom/batch-upload-advanced

请求参数:

  • files: MultipartFile[](文件数组)
  • institutionId: String(机构ID)

返回结果:

{
  "code": 200,
  "message": "上传成功",
  "data": {
    "batchId": "BATCH_1234567890",
    "totalFiles": 150,
    "studyCount": 2,
    "studies": [
      {
        "studyId": "STY1234567890",
        "studyInstanceUid": "1.2.840.113619.2.55.3...",
        "patientId": "P12345",
        "patientName": "张三",
        "modality": "CT",
        "bodyPart": "胸部",
        "fileCount": 100,
        "isPrimary": true
      },
      {
        "studyId": "STY1234567891",
        "studyInstanceUid": "1.2.840.113619.2.55.3...",
        "patientId": "P12345",
        "patientName": "张三",
        "modality": "MR",
        "bodyPart": "颅脑",
        "fileCount": 50,
        "isPrimary": false
      }
    ]
  }
}

🚀 使用示例

示例1:上传单个文件

curl -X POST http://localhost:8080/api/dicom/batch-upload-advanced \
  -F "files=@image.dcm" \
  -F "institutionId=INST001"

示例2:上传ZIP压缩包

curl -X POST http://localhost:8080/api/dicom/batch-upload-advanced \
  -F "files=@study.zip" \
  -F "institutionId=INST001"

示例3:上传多个文件(模拟文件夹)

curl -X POST http://localhost:8080/api/dicom/batch-upload-advanced \
  -F "files=@image1.dcm" \
  -F "files=@image2.dcm" \
  -F "files=@image3.dcm" \
  -F "institutionId=INST001"

⚙️ 配置说明

临时目录配置

压缩包解压时会使用系统临时目录:

# macOS/Linux
/tmp/dicom_upload_xxxxxxxx/

# Windows
C:\Users\{username}\AppData\Local\Temp\dicom_upload_xxxxxxxx\

文件存储根目录

private static final String DICOM_ROOT_PATH = System.getProperty("user.home") + "/qconline/dicom/";

📝 执行数据库迁移

# 连接到数据库
mysql -u root -p

# 选择数据库
use qconline;

# 执行SQL脚本
source /path/to/alter_study_info_add_batch_id.sql;

🎯 核心特性说明

1. 多Study自动识别

当上传的压缩包包含多个不同study_instance_uid的文件时:

  • 系统会自动识别并分组
  • 为每个不同的study_instance_uid创建独立的StudyInfo记录
  • 通过batch_id标识它们来自同一批上传
  • 通过parent_study_id标识主从关系

2. 文件去重

  • 基于study_instance_uid判断是否已存在
  • 如果已存在,可以选择覆盖或跳过(当前为覆盖逻辑)

3. 临时目录自动清理

  • 上传过程中创建临时目录
  • 上传完成后自动清理
  • 即使发生异常也会在finally块中清理

4. 事务保证

  • 整个批量上传过程使用@Transactional
  • 任何步骤失败都会回滚
  • 保证数据一致性

🔄 兼容性

保留旧接口

为了兼容现有系统,保留了旧的上传接口:

  • /api/dicom/upload - 单文件上传(旧)
  • /api/dicom/batch-upload - 批量上传(旧)

新接口

  • /api/dicom/batch-upload-advanced - 高级批量上传(新)

📊 日志输出示例

[INFO] 开始批量上传: institutionId=INST001, 文件数=1
[INFO] 批次ID: BATCH_1735189234567
[INFO] 检测到压缩包: study.zip
[INFO] 创建临时目录: /var/folders/.../dicom_upload_a1b2c3d4
[INFO] 开始解压文件: /var/folders/.../study.zip
[INFO] 解压完成,共解压 150 个文件
[INFO] 共收集到 150 个文件需要处理
[INFO] 识别到 2 个不同的study
[INFO] 处理study: studyInstanceUid=1.2.840..., 文件数=100
[INFO] 创建study目录: /Users/xxx/qconline/dicom/INST001/1.2.840.../
[INFO] 创建study记录: studyId=STY001, studyInstanceUid=1.2.840..., 文件数=100
[INFO] 处理study: studyInstanceUid=1.2.841..., 文件数=50
[INFO] 创建study目录: /Users/xxx/qconline/dicom/INST001/1.2.841.../
[INFO] 创建study记录: studyId=STY002, studyInstanceUid=1.2.841..., 文件数=50
[INFO] 批量上传完成: batchId=BATCH_1735189234567, 总文件数=150, study数=2
[INFO] 清理临时目录: /var/folders/.../dicom_upload_a1b2c3d4

✅ 后端完成清单

  • 创建数据库迁移SQL脚本
  • 修改StudyInfo实体类,添加batchId字段
  • 创建ArchiveUtil压缩包工具类
  • 创建BatchUploadVO批量上传结果类
  • 修改DicomService接口,添加批量上传方法
  • 在DicomServiceImpl中实现批量上传逻辑
  • 实现按study_instance_uid分组逻辑
  • 实现文件存储结构(使用study_instance_uid命名目录)
  • 在DicomController中添加批量上传接口
  • 添加batch_id和parent_study_id关联逻辑

📌 前端修改建议

1. 修改上传组件

使用Element Plus的el-upload组件,添加以下功能:

  • 支持选择文件夹(webkitdirectory属性)
  • 支持多文件选择
  • 显示上传进度
  • 显示上传结果

2. API调用

使用新的批量上传接口:

const formData = new FormData()
files.forEach(file => {
  formData.append('files', file)
})
formData.append('institutionId', institutionId)

axios.post('/api/dicom/batch-upload-advanced', formData)

🎉 总结

后端功能已全部实现,包括:

  • ✅ 压缩包解压
  • ✅ 多study识别和分组
  • ✅ 文件存储结构优化
  • ✅ batch_id和parent_study_id关联
  • ✅ 完整的日志记录
  • ✅ 异常处理和事务保证
  • ✅ 临时目录自动清理

接下来需要修改前端上传组件以使用新接口。