增加get请求
continuous-integration/drone/push Build is passing Details

Test_HIR_Net8
hang 2025-12-23 15:42:38 +08:00
parent a6f055bb1a
commit fe7c6e5dfa
1 changed files with 210 additions and 2 deletions

View File

@ -903,7 +903,7 @@ namespace IRaCIS.Core.API.Controllers
Response.Headers["Cache-Control"] = "no-store";
// ⚠️ 关键:直接用 Response.Body
using var zip = new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create,leaveOpen: true);
using var zip = new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create, leaveOpen: true);
// 本地大文件路径
var files = new[]
@ -919,7 +919,7 @@ namespace IRaCIS.Core.API.Controllers
var entryName = Path.GetFileName(phyFilePath);
var entry = zip.CreateEntry( entryName,
var entry = zip.CreateEntry(entryName,
CompressionLevel.Fastest); // 大文件建议 Fastest
await using var entryStream = entry.Open();
@ -938,6 +938,214 @@ namespace IRaCIS.Core.API.Controllers
}
[HttpPost("download/GetPatientStudyBatchDownload")]
public async Task<IActionResult> GetDownloadPatientStudyBatch([FromServices] IPatientService _patientService, [FromServices] IOSSService _oSSService,
[FromServices] IHubContext<DownloadHub, IDownloadClient> _downLoadHub,
[FromQuery] PatientImageDownloadCommand inCommand)
{
inCommand.SCPStudyIdList = inCommand.SCPStudyIdList.Where(t => t != Guid.Empty).ToList();
var rusult = await _patientService.GetDownloadPatientStudyInfo(inCommand);
var patientList = rusult.Data;
var downloadInfo = (SubejctVisitDownload)rusult.OtherData;
long receivedSize = 0;
long receivedCount = 0;
long totalSize = downloadInfo.ImageSize;
long totalCount = downloadInfo.ImageCount;
long failedCount = 0;
var abortToken = HttpContext.RequestAborted;
// -------- SignalR 节流参数 --------
var notifyInterval = TimeSpan.FromSeconds(1);
var lastNotify = DateTime.UtcNow;
// 用于计算下载速度
long lastReceivedSize = 0;
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;
// 计算下载速度(字节/秒)
double speedBps = 0;
if (elapsedSeconds > 0)
{
speedBps = (receivedSize - lastReceivedSize) / elapsedSeconds;
}
lastSpeedCheck = now;
lastReceivedSize = receivedSize;
lastNotify = DateTime.UtcNow;
var progress = new
{
FailedCount = failedCount,
TotalCount = totalCount,
TotalSize = (totalSize / 1024 / 1024).ToString("0.00") + " MB",
CountPercent = totalCount > 0
? Math.Round(receivedCount * 100m / totalCount, 2).ToString()
: "0",
SizePercent = totalSize > 0
? Math.Round(receivedSize * 100m / totalSize, 2).ToString()
: "0",
Speed = (speedBps / 1024 >= 1024
? (speedBps / 1024 / 1024).ToString("0.00") + " MB/s"
: (speedBps / 1024).ToString("0.00") + " KB/s")
};
// 不阻塞下载流程
_ = _downLoadHub.Clients
.User(_userInfo.IdentityUserId.ToString())
.ReceivProgressAsync(
inCommand.CurrentNoticeId,
progress
);
}
Response.ContentType = "application/zip";
Response.Headers["Content-Disposition"] = $"attachment; filename=Image_{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 patient in patientList)
{
foreach (var study in patient.StudyList)
{
abortToken.ThrowIfCancellationRequested();
var studyTime = study.StudyTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "UnknownTime";
var modalitysStr = string.Join('_', study.SeriesList.Select(t => t.Modality).Distinct());
// ---------- DICOMDIR ----------
var dicomDirPath = $"{patient.PatientIdStr}/{studyTime}_{modalitysStr}/DICOMDIR";
var dicomDirEntry = zip.CreateEntry(dicomDirPath, CompressionLevel.Fastest);
await using (var entryStream = dicomDirEntry.Open())
await using (var dirStream = await _oSSService.GetStreamFromOSSAsync(study.StudyDIRPath))
{
await dirStream.CopyToAsync(entryStream, 32 * 1024, abortToken);
}
// ---------- IMAGE FILES ----------
foreach (var series in study.SeriesList)
{
foreach (var instance in series.InstanceList)
{
abortToken.ThrowIfCancellationRequested();
try
{
//当前完成大小
receivedSize = receivedSize + instance.FileSize ?? 0;
receivedCount++;
var entryPath =
$"{patient.PatientIdStr}/{studyTime}_{modalitysStr}/IMAGE/{instance.FileName}";
var entry = zip.CreateEntry(entryPath, CompressionLevel.Fastest);
await using var entryStream = entry.Open();
await using var source = await _oSSService.GetStreamFromOSSAsync(instance.Path);
#region 将多帧合并为一帧
// 如果你是从 stream 打开
var dicomFile = await DicomFile.OpenAsync(source);
// 获取 Pixel Data 标签
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
var frag = dicomFile.Dataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData);
var originOffsetTable = frag.OffsetTable;
//获取像素是否为封装形式
var syntax = dicomFile.Dataset.InternalTransferSyntax;
//对于封装像素的文件做转换
if (syntax.IsEncapsulated)
{
// 创建一个新的片段序列
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
// 获取每帧数据并封装为单独的片段
for (int n = 0; n < pixelData.NumberOfFrames; n++)
{
var frameData = pixelData.GetFrame(n);
newFragments.Fragments.Add(new MemoryByteBuffer(frameData.Data));
}
newFragments.OffsetTable.AddRange(originOffsetTable.ToArray());
// 替换原有的片段序列
dicomFile.Dataset.AddOrUpdate(newFragments);
}
#endregion
await dicomFile.SaveAsync(entryStream);
//await source.CopyToAsync(entryStream, 32 * 1024, abortToken);
}
catch (Exception ex)
{
failedCount++;
Log.Logger.Warning($"处理文件{instance.Path}失败: {ex.Message}");
}
finally
{
await NotifyProgressAsync();
}
}
}
}
}
// 正常完成
await NotifyProgressAsync(true);
await _patientService.DownloadImageSuccess(downloadInfo.Id);
}
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();
}
[HttpPost("download/PatientStudyBatchDownload")]
public async Task<IActionResult> DownloadPatientStudyBatch([FromServices] IPatientService _patientService, [FromServices] IOSSService _oSSService,