通过Excel 读取信息进行下载
continuous-integration/drone/push Build is running Details

Test_IRC_Net8
hang 2026-06-02 01:17:01 +08:00
parent e11790ea92
commit 9d4c69b39a
1 changed files with 385 additions and 1 deletions

View File

@ -7,6 +7,7 @@ using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.Imaging.Render;
using FellowOakDicom.IO.Buffer;
using Hangfire.Common;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
@ -19,7 +20,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using NPOI.Util;
using Org.BouncyCastle.Utilities.Zlib;
using SharpCompress.Common;
using System;
@ -34,6 +34,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using static IRaCIS.Core.Application.Service.TestService;
using static IRaCIS.Core.Application.Service.TrialImageDownloadService;
using static IRaCIS.Core.Domain.Share.StaticData;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
@ -165,6 +166,389 @@ namespace IRaCIS.Core.Application.Service
public Func<Task> Action { get; set; }
}
[HttpPost]
[AllowAnonymous]
public async Task<IResponseOutput> DownloadExcelTrialImage(Guid trialId)
{
var trialInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo }).FirstOrDefault();
#region 设置目录
var rootFolder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
Directory.CreateDirectory(rootFolder);
// 获取无效字符(系统定义的)
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
// 用正则表达式替换所有非法字符为下划线或空字符
string pattern = $"[{Regex.Escape(invalidChars)}]";
var regexNo = Regex.Replace(trialInfo.ResearchProgramNo, pattern, "_");
// 创建一个临时文件夹来存放文件
string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}");//_{NewId.NextGuid()}
Directory.CreateDirectory(trialFolderPath);
#endregion
var downloadVisits = MiniExcel.Query<SubjectVisitExcel>(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty());
var downloadJobs = new List<DownloadJob>();
foreach (var downloadVisit in downloadVisits)
{
var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new
{
t.ResearchProgramNo,
t.TrialCode,
VisitList = t.SubjectVisitList.Where(t => t.VisitName.Trim() == downloadVisit.VisitName.Trim() && t.Subject.Code.Trim() == downloadVisit.SubjectCode.Trim() && t.VisitNum == downloadVisit.VisitNum)
.Select(sv => new
{
SubjectVisitId = sv.Id,
TrialSiteCode = sv.TrialSite.TrialSiteCode,
SubjectCode = sv.Subject.Code,
VisitName = sv.VisitName,
VisitNum = sv.VisitNum,
StudyList = sv.StudyList.Select(u => new
{
StudyId = u.Id,
u.PatientId,
u.StudyTime,
u.StudyCode,
u.StudyInstanceUid,
u.StudyDIRPath,
SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new
{
z.Modality,
InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new
{
InstanceId = k.Id,
k.Path,
k.IsEncapsulated,
k.NumberOfFrames,
}).ToList()
})
}).ToList(),
NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new
{
nd.Modality,
nd.StudyCode,
nd.ImageDate,
FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new
{
file.FileName,
file.Path,
file.FileType
}).ToList()
}).ToList()
}).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList()
}).FirstOrDefault();
if (downloadInfo == null)
{
return ResponseOutput.Ok();
}
#region 排除已经下载的
var logFilePath = Path.Combine(rootFolder, $"{trialId}_{regexNo}_download_log.csv");
if (File.Exists(logFilePath))
{
var existVisits = MiniExcel.Query<SubjectVisitExcel>(logFilePath, configuration: new MiniExcelLibs.Csv.CsvConfiguration()
{
StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312"))
}).ToList();
if (existVisits.Any(old => old.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode &&
old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower()))
{
continue;
}
}
#endregion
foreach (var visitItem in downloadInfo.VisitList)
{
if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0)
{
continue;
}
foreach (var studyInfo in visitItem.StudyList)
{
var dirDic = new Dictionary<string, string>();
#region 生成DIR
string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality))}");
// 创建 影像 文件夹
Directory.CreateDirectory(studyDicomFolderPath);
if (!_instanceRepository.Where(t => t.IsReading && t.DicomSerie.IsReading)
.Where(t => visitItem.SubjectVisitId == t.SubjectVisitId).Any(c => c.TransferSytaxUID == string.Empty))
{
var list = _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).SelectMany(t => t.StudyList)
.SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading && t.StudyId == studyInfo.StudyId))
.Select(t => new StudyDIRInfo()
{
DicomStudyId = t.DicomStudy.Id,
PatientId = downloadInfo.TrialCode + "-" + t.DicomStudy.Subject.Code,
PatientName = t.DicomStudy.PatientName,
PatientBirthDate = t.DicomStudy.PatientBirthDate,
PatientSex = t.DicomStudy.PatientSex,
StudyInstanceUid = t.StudyInstanceUid,
StudyId = t.DicomStudy.StudyId,
DicomStudyDate = t.DicomStudy.DicomStudyDate,
DicomStudyTime = t.DicomStudy.DicomStudyTime,
AccessionNumber = t.DicomStudy.AccessionNumber,
StudyDescription = t.DicomStudy.Description,
SeriesInstanceUid = t.DicomSerie.SeriesInstanceUid,
Modality = t.DicomSerie.Modality,
DicomSeriesDate = t.DicomSerie.DicomSeriesDate,
DicomSeriesTime = t.DicomSerie.DicomSeriesTime,
SeriesNumber = t.DicomSerie.SeriesNumber,
SeriesDescription = t.DicomSerie.Description,
InstanceId = t.Id,
SopInstanceUid = t.SopInstanceUid,
SOPClassUID = t.SOPClassUID,
InstanceNumber = t.InstanceNumber,
MediaStorageSOPClassUID = t.MediaStorageSOPClassUID,
MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID,
TransferSytaxUID = t.TransferSytaxUID,
}).ToList();
var pathInfo = await _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).Select(t => new { t.TrialId, t.SubjectId, VisitId = t.Id }).FirstNotNullAsync();
foreach (var group in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
{
var first = group.First();
var dirPath = Path.Combine(studyDicomFolderPath, "DICOMDIR");
var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, dirPath));
}
}
#endregion
// 遍历 Series
foreach (var seriesInfo in studyInfo.SeriesList)
{
// 遍历 InstancePathList
foreach (var instanceInfo in seriesInfo.InstancePathList)
{
string destinationFolder = Path.Combine(studyDicomFolderPath, "IMAGE");
Directory.CreateDirectory(destinationFolder);
// 复制文件到相应的文件夹
string destinationPath = Path.Combine(destinationFolder, dirDic[instanceInfo.InstanceId.ToString()]);
downloadJobs.Add(new DownloadJob()
{
Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_DICOM_{destinationPath}",
Action = async () =>
{
await using var output = File.Create(destinationPath);
if (instanceInfo.IsEncapsulated)
{
var isSucess = await TryWriteMergedDicomAsync(
() => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path),
output);
if (isSucess == false)
{
Log.Logger.Warning($"合并多帧失败: failed: {destinationFolder} {destinationPath}");
}
}
else
{
await using var input =
await _oSSService.GetStreamFromOSSAsync(instanceInfo.Path);
await input.CopyToAsync(output);
}
}
});
}
}
}
foreach (var noneDicomStudy in visitItem.NoneDicomStudyList)
{
string studyNoneDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}");
Directory.CreateDirectory(studyNoneDicomFolderPath);
foreach (var file in noneDicomStudy.FileList)
{
string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName));
//加入到下载任务里
downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_NoneDICOM_{destinationPath}", Action = () => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath) });
//下载到当前目录
//await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath);
}
}
//建立压缩包
string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}");
downloadJobs.Add(new DownloadJob()
{
Name = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}_Zip",
IsZip = true,
Action = async () =>
{
Log.Logger.Warning($" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}");
string zipPath = visitFolderPath + ".zip";
if (File.Exists(zipPath))
{
File.Delete(zipPath);
}
ZipFile.CreateFromDirectory(
visitFolderPath,
zipPath,
CompressionLevel.NoCompression,
false);
Directory.Delete(visitFolderPath, true);
await Task.CompletedTask;
}
});
//记录日志
downloadJobs.Add(new DownloadJob()
{
Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_Finished",
Action = () =>
{
DownloadLogger.Write(
logFilePath: logFilePath,
subjectCode: visitItem.SubjectCode,
visitNum: visitItem.VisitNum,
visitName: visitItem.VisitName.Trim(),
message: "Success");
return Task.CompletedTask;
}
});
}
}
#region 异步方式处理
int totalCount = downloadJobs.Count;
int downloadedCount = 0;
Log.Logger.Warning($"开始下载总数: {totalCount}");
foreach (var job in downloadJobs)
{
try
{
if (job.IsZip == false)
{
await job.Action();
}
else
{
_ = Task.Run(async () =>
{
await job.Action();
});
}
}
catch (Exception ex)
{
Log.Logger.Error($"{job.Name}下载失败: {ex.Message}");
}
downloadedCount++;
// 每处理50个输出一次进度或最后一个时也输出
if (downloadedCount % 50 == 0 || downloadedCount == totalCount)
{
Log.Logger.Warning($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%");
}
}
#endregion
return ResponseOutput.Ok();
}
/// <summary>
/// 后端api swagger 下载项目影像
/// </summary>