diff --git a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs
index 0c637634c..959b403cf 100644
--- a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs
+++ b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs
@@ -1285,26 +1285,134 @@ namespace IRaCIS.Core.API.Controllers
}
}
- /////
- ///// 下载阅片报告和图片
- /////
- /////
- /////
- /////
- /////
- /////
- //[HttpPost("download/ReadingReportAndImage")]
- //public async Task DownloadReadingReportAndImage([FromServices] IReadingImageTaskService _readingImageTaskService, [FromServices] IOSSService _oSSService,
- // [FromServices] IHubContext _downLoadHub,
- // [FromForm] GetReadingReportAndImageInDto inCommand)
- //{
- // Response.ContentType = "application/zip";
- // Response.Headers["Content-Disposition"] = $"attachment; filename=Image_{ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId)}.zip";
- // Response.Headers["Cache-Control"] = "no-store";
+ ///
+ /// 下载阅片报告和图片
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpPost("download/ReadingReportAndImage")]
+ public async Task DownloadReadingReportAndImage([FromServices] IReadingImageTaskService _readingImageTaskService, [FromServices] IOSSService _oSSService,
+ [FromServices] IHubContext _downLoadHub,
+ [FromForm] GetReadingReportAndImageInDto inCommand)
+ {
+ Response.ContentType = "application/zip";
+ Response.Headers["Content-Disposition"] = $"attachment; filename=Image_{ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId)}.zip";
+ Response.Headers["Cache-Control"] = "no-store";
- // var data = await _readingImageTaskService.GetReadingReportAndImage(inCommand);
+ var data = await _readingImageTaskService.GetReadingReportAndImage(inCommand);
+ var abortToken = HttpContext.RequestAborted;
- //}
+ string SanitizePathSegment(string? value, string defaultValue)
+ {
+ var result = string.IsNullOrWhiteSpace(value) ? defaultValue : value.Trim();
+ foreach (var invalidChar in Path.GetInvalidFileNameChars())
+ {
+ result = result.Replace(invalidChar, '_');
+ }
+
+ return string.IsNullOrWhiteSpace(result) ? defaultValue : result;
+ }
+
+ string GetEntryFileName(string? sourcePath, string defaultPrefix, int index)
+ {
+ var fileName = Path.GetFileName(sourcePath ?? string.Empty);
+ if (string.IsNullOrWhiteSpace(fileName))
+ {
+ fileName = $"{defaultPrefix}_{index}";
+ }
+
+ fileName = SanitizePathSegment(fileName, $"{defaultPrefix}_{index}");
+ return fileName;
+ }
+
+ string BuildUniqueEntryPath(HashSet existingEntryPaths, string folderPath, string fileName)
+ {
+ var extension = Path.GetExtension(fileName);
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+ var candidatePath = $"{folderPath}/{fileName}";
+ var suffix = 1;
+
+ while (!existingEntryPaths.Add(candidatePath))
+ {
+ candidatePath = $"{folderPath}/{fileNameWithoutExtension}_{suffix}{extension}";
+ suffix++;
+ }
+
+ return candidatePath;
+ }
+
+ try
+ {
+ await using var responseStream = Response.BodyWriter.AsStream();
+ using var zip = new ZipArchive(responseStream, ZipArchiveMode.Create, leaveOpen: true);
+ var existingEntryPaths = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var item in data)
+ {
+ abortToken.ThrowIfCancellationRequested();
+
+ var folderName =
+ $"{SanitizePathSegment(item.SubjectCode, "UnknownSubject")}_{SanitizePathSegment(item.VisitName, "UnknownVisit")}";
+
+ foreach (var reportUrl in item.ReportUrlList.Where(x => !string.IsNullOrWhiteSpace(x)))
+ {
+ abortToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ var fileName = GetEntryFileName(reportUrl, "Report", existingEntryPaths.Count + 1);
+ var entryPath = BuildUniqueEntryPath(existingEntryPaths, folderName, fileName);
+ var entry = zip.CreateEntry(entryPath, CompressionLevel.Fastest);
+
+ await using var entryStream = entry.Open();
+ await using var source = await _oSSService.GetStreamFromOSSAsync(reportUrl);
+ await source.CopyToAsync(entryStream, 32 * 1024, abortToken);
+ }
+ catch (Exception ex)
+ {
+ Log.Logger.Warning($"处理阅片报告文件{reportUrl}失败: {ex.Message}");
+ }
+ }
+
+ foreach (var imageUrl in item.ImageUrlList.Where(x => !string.IsNullOrWhiteSpace(x)))
+ {
+ abortToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ var fileName = GetEntryFileName(imageUrl, "Image", existingEntryPaths.Count + 1);
+ var entryPath = BuildUniqueEntryPath(existingEntryPaths, folderName, fileName);
+ var entry = zip.CreateEntry(entryPath, CompressionLevel.Fastest);
+
+ await using var entryStream = entry.Open();
+ await using var source = await _oSSService.GetStreamFromOSSAsync(imageUrl);
+ await source.CopyToAsync(entryStream, 32 * 1024, abortToken);
+ }
+ catch (Exception ex)
+ {
+ Log.Logger.Warning($"处理阅片图片文件{imageUrl}失败: {ex.Message}");
+ }
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ Log.Logger.Warning("Download reading report and image canceled by client");
+ }
+ catch (IOException ex) when (abortToken.IsCancellationRequested)
+ {
+ Log.Logger.Warning($"Client disconnected: {ex.Message}");
+ }
+ catch (NullReferenceException ex) when (abortToken.IsCancellationRequested)
+ {
+ Log.Logger.Warning($"Stream aborted: {ex.Message}");
+ }
+
+ return new EmptyResult();
+ }
[HttpPost("download/PatientStudyBatchDownloadForm")]
public async Task DownloadPatientStudyBatchForm([FromServices] IPatientService _patientService, [FromServices] IOSSService _oSSService,
diff --git a/IRaCIS.Core.API/IRaCIS.Core.API.xml b/IRaCIS.Core.API/IRaCIS.Core.API.xml
index 7ef85e91f..fb0c367d5 100644
--- a/IRaCIS.Core.API/IRaCIS.Core.API.xml
+++ b/IRaCIS.Core.API/IRaCIS.Core.API.xml
@@ -337,6 +337,16 @@
+
+
+ 下载阅片报告和图片
+
+
+
+
+
+
+
IPLimit限流 启动服务
diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
index 29cd78986..ea4918a9e 100644
--- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
+++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
@@ -7817,6 +7817,26 @@
值
+
+
+ 受试者编号
+
+
+
+
+ 访视名称
+
+
+
+
+ 报告文件的URL地址列表
+
+
+
+
+ 图片的URl地址列表
+
+
任务Id
@@ -12902,6 +12922,13 @@
IR影像阅片
+
+
+ 获取阅片报告数据和图像
+
+
+
+
获取TNMValue
@@ -14951,17 +14978,17 @@
- 质疑
+ ����
- 一致性核查
+ һ���Ժ˲�
- 复制
+ ����
diff --git a/IRaCIS.Core.Application/Service/Reading/Dto/ReadingCalculateViewModel.cs b/IRaCIS.Core.Application/Service/Reading/Dto/ReadingCalculateViewModel.cs
index 4b1c6c754..0916a4647 100644
--- a/IRaCIS.Core.Application/Service/Reading/Dto/ReadingCalculateViewModel.cs
+++ b/IRaCIS.Core.Application/Service/Reading/Dto/ReadingCalculateViewModel.cs
@@ -300,12 +300,24 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto
public class ReadingReportAndImage
{
+ ///
+ /// 受试者编号
+ ///
public string SubjectCode { get; set; }
+ ///
+ /// 访视名称
+ ///
public string VisitName { get; set; }
+ ///
+ /// 报告文件的URL地址列表
+ ///
public List ReportUrlList { get; set; } = new List();
+ ///
+ /// 图片的URl地址列表
+ ///
public List ImageUrlList { get; set; } = new List();
}
diff --git a/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs b/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs
index 302f1247a..c212dfe56 100644
--- a/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs
+++ b/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs
@@ -92,8 +92,11 @@ namespace IRaCIS.Core.Application.Service
}
else
{
- taskList = await _visitTaskRepository.Where(x => x.TrialReadingCriterionId==inDto.TrialCriterionId&&x.TaskState== TaskState.Effect && x.ReadingTaskState == ReadingTaskState.HaveSigned && x.ReadingCategory == ReadingCategory.Visit)
- .Include(x => x.LesionList).Include(x => x.Subject).Include(x => x.SourceSubjectVisit).ToListAsync();
+ taskList = await _visitTaskRepository.Where(x => x.TrialReadingCriterionId==inDto.TrialCriterionId && x.ReadingCategory == ReadingCategory.Visit && x.TaskState== TaskState.Effect && x.ReadingTaskState == ReadingTaskState.HaveSigned && x.ReadingCategory == ReadingCategory.Visit)
+ .Include(x => x.LesionList)
+ .Include(x => x.Subject)
+ .Include(x => x.SourceSubjectVisit)
+ .ToListAsync();
}
List< ReadingReportAndImage > result = new List() { };