From bd9d1a62980e47cb8059631f9e85bf6ea35d2006 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 17 Dec 2025 17:42:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=BF=E8=A7=86=E4=B8=8B?= =?UTF-8?q?=E8=BD=BDbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/UploadDownLoadController.cs | 133 +++++++++++++++++- .../Interface/IReadingClinicalDataService.cs | 2 + .../Service/Visit/DTO/PatientViewModel.cs | 7 + .../Service/Visit/PatientService.cs | 2 +- 4 files changed, 139 insertions(+), 5 deletions(-) diff --git a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs index c31d31923..f5b71c0da 100644 --- a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs +++ b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs @@ -944,11 +944,11 @@ namespace IRaCIS.Core.API.Controllers var progress = new { CountPercent = totalCount > 0 - ? Math.Round(receivedCount * 100m / totalCount, 2).ToString() + ? Math.Round(receivedCount * 100m / totalCount, 2).ToString() : "0", - SizePercent = totalSize > 0 - ? Math.Round(receivedSize * 100m / totalSize, 2).ToString() + SizePercent = totalSize > 0 + ? Math.Round(receivedSize * 100m / totalSize, 2).ToString() : "0", Speed = (speedBps / 1024 >= 1024 @@ -972,6 +972,9 @@ namespace IRaCIS.Core.API.Controllers try { + //开始推送一次 + await NotifyProgressAsync(true); + await using var responseStream = Response.BodyWriter.AsStream(); using var zip = new ZipArchive(responseStream, ZipArchiveMode.Create, leaveOpen: true); @@ -1090,7 +1093,7 @@ namespace IRaCIS.Core.API.Controllers var trialInfo = _trialRepository.Where(t => t.Id == inCommand.TrialId).Select(t => new { t.TrialCode, t.ResearchProgramNo }).FirstOrDefault(); - var trialZipName = $"{trialInfo.TrialCode}_{trialInfo.ResearchProgramNo}_Image_"; + var trialZipName = $"{trialInfo.TrialCode}_{trialInfo.ResearchProgramNo}_Image_"; long receivedSize = 0; long receivedCount = 0; @@ -1159,6 +1162,9 @@ namespace IRaCIS.Core.API.Controllers try { + //开始推送一次 + await NotifyProgressAsync(true); + await using var responseStream = Response.BodyWriter.AsStream(); using var zip = new ZipArchive(responseStream, ZipArchiveMode.Create, leaveOpen: true); @@ -1262,6 +1268,125 @@ namespace IRaCIS.Core.API.Controllers } + + + [HttpPost("download/ClinicalDataDownload")] + public async Task ClinicalDataDownload([FromServices] IReadingClinicalDataService _readingClinicalDataService, [FromServices] IOSSService _oSSService, +[FromServices] IHubContext _downLoadHub, +[FromServices] IRepository _trialReadingCriterionRepository, +ClinicalDataDownloadDTO inCommand) + { + var dataList = await _readingClinicalDataService.GetTrialClinicalDataSelect(inCommand); + + + var trialInfo = _trialReadingCriterionRepository.Where(t => t.Id == inCommand.TrialReadingCriterionId).Select(t => new { t.Trial.TrialCode, t.Trial.ResearchProgramNo, t.CriterionName }).FirstOrDefault(); + + var trialZipName = $"{trialInfo.TrialCode}_{trialInfo.ResearchProgramNo}_{trialInfo.CriterionName}_template_"; + + long receivedCount = 0; + + long totalCount = dataList.Count; + + var abortToken = HttpContext.RequestAborted; + + // -------- SignalR 节流参数 -------- + var notifyInterval = TimeSpan.FromSeconds(1); + var lastNotify = DateTime.UtcNow; + + // 用于计算下载速度 + DateTime lastSpeedCheck = DateTime.UtcNow; + + async Task NotifyProgressAsync(bool force = false) + { + + var now = DateTime.UtcNow; + var elapsedSeconds = (now - lastSpeedCheck).TotalSeconds; + + // 如果没有强制推送,并且未到推送间隔,则返回 + if (!force && elapsedSeconds < notifyInterval.TotalSeconds) + return; + + + + lastSpeedCheck = now; + lastNotify = DateTime.UtcNow; + + var progress = new + { + CountPercent = totalCount > 0 + ? Math.Round(receivedCount * 100m / totalCount, 2).ToString() + : "0", + + }; + + // 不阻塞下载流程 + _ = _downLoadHub.Clients + .User(_userInfo.IdentityUserId.ToString()) + .ReceivProgressAsync( + inCommand.CurrentNoticeId, + progress + ); + } + + Response.ContentType = "application/zip"; + Response.Headers["Content-Disposition"] = $"attachment; filename={trialZipName}{ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId)}.zip"; + Response.Headers["Cache-Control"] = "no-store"; + + + try + { + //开始推送一次 + await NotifyProgressAsync(true); + + await using var responseStream = Response.BodyWriter.AsStream(); + using var zip = new ZipArchive(responseStream, ZipArchiveMode.Create, leaveOpen: true); + + foreach (var data in dataList) + { + + abortToken.ThrowIfCancellationRequested(); + + //当前完成大小 + receivedCount++; + + var entryPath =$"{data.FileName}"; + + var entry = zip.CreateEntry(entryPath, CompressionLevel.Fastest); + + await using var entryStream = entry.Open(); + await using var source = await _oSSService.GetStreamFromOSSAsync(data.Path); + + + await source.CopyToAsync(entryStream, 32 * 1024, abortToken); + + await NotifyProgressAsync(); + + } + + // 正常完成 + await NotifyProgressAsync(true); + } + catch (OperationCanceledException) + { + // ✅ 客户端取消 / 断开 —— 正常情况 + Log.Logger.Warning("Download canceled by client"); + } + catch (IOException ex) when (abortToken.IsCancellationRequested) + { + // ✅ HttpClient 流在中断时的常见异常 + Log.Logger.Warning($"Client disconnected: {ex.Message}"); + } + catch (NullReferenceException ex) when (abortToken.IsCancellationRequested) + { + // ✅ HttpConnection.ContentLengthReadStream 已知问题 + Log.Logger.Warning($"Stream aborted: {ex.Message}"); + } + + return new EmptyResult(); + + + } + } } diff --git a/IRaCIS.Core.Application/Service/Reading/Interface/IReadingClinicalDataService.cs b/IRaCIS.Core.Application/Service/Reading/Interface/IReadingClinicalDataService.cs index 16c618a2c..9ea060fb6 100644 --- a/IRaCIS.Core.Application/Service/Reading/Interface/IReadingClinicalDataService.cs +++ b/IRaCIS.Core.Application/Service/Reading/Interface/IReadingClinicalDataService.cs @@ -31,5 +31,7 @@ namespace IRaCIS.Core.Application.Contracts Task SignConsistencyAnalysisReadingClinicalData(SignConsistencyAnalysisReadingClinicalDataInDto inDto); + Task> GetTrialClinicalDataSelect(GetTrialClinicalDataSelectIndto inDto); + } } \ No newline at end of file diff --git a/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs b/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs index e373533be..ed0661f3f 100644 --- a/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs +++ b/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs @@ -1,4 +1,5 @@ using IRaCIS.Core.Application.Contracts.Dicom.DTO; +using IRaCIS.Core.Application.Service.Reading.Dto; using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infrastructure.Extention; @@ -965,6 +966,12 @@ namespace IRaCIS.Application.Contracts } + public class ClinicalDataDownloadDTO: GetTrialClinicalDataSelectIndto + { + [NotDefault] + public string CurrentNoticeId { get; set; } + } + public class VisitImageDownloadCommand { public Guid TrialId { get; set; } diff --git a/IRaCIS.Core.Application/Service/Visit/PatientService.cs b/IRaCIS.Core.Application/Service/Visit/PatientService.cs index a27578711..2313fa8de 100644 --- a/IRaCIS.Core.Application/Service/Visit/PatientService.cs +++ b/IRaCIS.Core.Application/Service/Visit/PatientService.cs @@ -3598,7 +3598,7 @@ namespace IRaCIS.Application.Services await _subejctVisitDownloadRepository.AddAsync(preDownloadInfo, true); - return ResponseOutput.Ok(visitList, preDownloadInfo.Id); + return ResponseOutput.Ok(visitList, preDownloadInfo); } ///