增加get请求
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
a6f055bb1a
commit
fe7c6e5dfa
|
|
@ -903,7 +903,7 @@ namespace IRaCIS.Core.API.Controllers
|
||||||
Response.Headers["Cache-Control"] = "no-store";
|
Response.Headers["Cache-Control"] = "no-store";
|
||||||
|
|
||||||
// ⚠️ 关键:直接用 Response.Body
|
// ⚠️ 关键:直接用 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[]
|
var files = new[]
|
||||||
|
|
@ -919,7 +919,7 @@ namespace IRaCIS.Core.API.Controllers
|
||||||
|
|
||||||
var entryName = Path.GetFileName(phyFilePath);
|
var entryName = Path.GetFileName(phyFilePath);
|
||||||
|
|
||||||
var entry = zip.CreateEntry( entryName,
|
var entry = zip.CreateEntry(entryName,
|
||||||
CompressionLevel.Fastest); // 大文件建议 Fastest
|
CompressionLevel.Fastest); // 大文件建议 Fastest
|
||||||
|
|
||||||
await using var entryStream = entry.Open();
|
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")]
|
[HttpPost("download/PatientStudyBatchDownload")]
|
||||||
public async Task<IActionResult> DownloadPatientStudyBatch([FromServices] IPatientService _patientService, [FromServices] IOSSService _oSSService,
|
public async Task<IActionResult> DownloadPatientStudyBatch([FromServices] IPatientService _patientService, [FromServices] IOSSService _oSSService,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue