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() { };