From aed778bbc6fa4343b8f8857efd5ee5e67439a410 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 28 Oct 2025 15:08:23 +0800 Subject: [PATCH] =?UTF-8?q?HIR=20=E5=AF=B9=E6=8E=A5=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E5=88=9D=E6=AD=A5=E6=B5=8B=E8=AF=95=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRC.Core.SCP/Service/CStoreSCPService.cs | 147 ++++++++++-------- IRC.Core.SCP/appsettings.Test_HIR_SCP.json | 25 ++- .../HostService/DicomSCPService.cs | 73 +++++---- IRaCIS.Core.API/Progranm.cs | 2 + IRaCIS.Core.API/appsettings.Test_HIR.json | 4 +- 5 files changed, 144 insertions(+), 107 deletions(-) diff --git a/IRC.Core.SCP/Service/CStoreSCPService.cs b/IRC.Core.SCP/Service/CStoreSCPService.cs index 1c458e436..2a06c1575 100644 --- a/IRC.Core.SCP/Service/CStoreSCPService.cs +++ b/IRC.Core.SCP/Service/CStoreSCPService.cs @@ -27,6 +27,7 @@ using FellowOakDicom.Imaging.Codec; using FellowOakDicom.IO.Buffer; using System.Diagnostics.CodeAnalysis; using FellowOakDicom.Network.Client; +using MassTransit.Futures.Contracts; namespace IRaCIS.Core.SCP.Service { @@ -35,14 +36,7 @@ namespace IRaCIS.Core.SCP.Service { public bool IsSupportThirdService { get; set; } - public string ThirdSearchPacsAE { get; set; } - - public string ThirdCallningAE { get; set; } - - public string ThirdIP { get; set; } - - public int THirdPort { get; set; } - + public List ThirdDestinationAEList { get; set; } public List CalledAEList { get; set; } @@ -50,6 +44,15 @@ namespace IRaCIS.Core.SCP.Service public string ServerPort { get; set; } } + public class ThirdDestinationAE + { + public int Port { get; set; } + + public string Name { get; set; } + + public string IP { get; set; } + } + @@ -120,9 +123,6 @@ namespace IRaCIS.Core.SCP.Service Log.Logger.Warning($"接收到来自{association.CallingAE}的连接"); - //_serviceProvider = (IServiceProvider)this.UserState; - - //var tt = _injectServiceProvider; var option = _serviceProvider.GetService>().CurrentValue; @@ -175,23 +175,27 @@ namespace IRaCIS.Core.SCP.Service public async Task OnReceiveAssociationReleaseRequestAsync() { - var _distributedLockProvider = _serviceProvider.GetService(); - - - var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}"); - - using (await @lock.AcquireAsync()) + if (_isCurrentThirdForward == false) { + var _distributedLockProvider = _serviceProvider.GetService(); - await DataMaintenanceAsaync(); - await AddUploadLogAsync(); + var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}"); - _releasedNormally = true; + using (await @lock.AcquireAsync()) + { - Log.Logger.Information($"进入释放连接请求 {_releasedNormally}"); + await DataMaintenanceAsaync(); + + await AddUploadLogAsync(); + + _releasedNormally = true; + + Log.Logger.Information($"进入释放连接请求 {_releasedNormally}"); + } } + await SendAssociationReleaseResponseAsync(); } @@ -273,26 +277,28 @@ namespace IRaCIS.Core.SCP.Service public async void OnConnectionClosed(Exception exception) { - var _studyRepository = _serviceProvider.GetService>(); - - - if (exception == null || _releasedNormally == false) + if (_isCurrentThirdForward == false) { - //客户端断网,恢复后,也是没有异常的,估计是超时走了关闭 + var _studyRepository = _serviceProvider.GetService>(); - await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true }); - //记录日志 - await AddUploadLogAsync(); + + if (exception == null || _releasedNormally == false) + { + //客户端断网,恢复后,也是没有异常的,估计是超时走了关闭 + + await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true }); + //记录日志 + await AddUploadLogAsync(); + } + else + { + //将检查设置为传输结束 + await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false }); + } + + + await _studyRepository.SaveChangesAndClearAllTrackingAsync(); } - else - { - //将检查设置为传输结束 - await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false }); - } - - - await _studyRepository.SaveChangesAndClearAllTrackingAsync(); - Log.Logger.Warning($"连接关闭 {_releasedNormally} {exception?.Message} {exception?.InnerException?.Message}"); } @@ -319,46 +325,53 @@ namespace IRaCIS.Core.SCP.Service #region 判断是否转发第三方影像 - var cmoveInfo = _cmoveStudyRepository.Where(t => t.StudyInstanceUIDList.Any(c => c == studyInstanceUid)).OrderByDescending(t => t.CreateTime).FirstOrDefault(); - - //确定是第三方请求 - if (cmoveInfo != null && cmoveInfo.CallingAE == DicomSCPServiceConfig.ThirdCallningAE) + if (DicomSCPServiceConfig.IsSupportThirdService) { - _isCurrentThirdForward = true; + var cmoveInfo = _cmoveStudyRepository.Where(t => t.StudyInstanceUIDList.Any(c => c == studyInstanceUid)).OrderByDescending(t => t.CreateTime).FirstOrDefault(); - var _dicomAERepository = _serviceProvider.GetService>(); - var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer); - - try + //确定是第三方请求 + if (cmoveInfo != null && DicomSCPServiceConfig.ThirdDestinationAEList.Any(t => t.Name == cmoveInfo.DestinationAE)) { - // 克隆 Dataset 避免共享引用 - var dsCopy = request.Dataset?.Clone(); + _isCurrentThirdForward = true; - var forwardRequest = new DicomCStoreRequest(dsCopy); + var _dicomAERepository = _serviceProvider.GetService>(); + var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer); - - // 创建客户端连接到目标 PACS - var client = DicomClientFactory.Create(DicomSCPServiceConfig.ThirdIP, DicomSCPServiceConfig.THirdPort, false, DicomSCPServiceConfig.CalledAEList.First(), cmoveInfo.DestinationAE); - - // 可以加入 OnResponseReceived 来处理目标 PACS 返回状态 - forwardRequest.OnResponseReceived += (rq, rp) => + try { - Log.Logger.Information($"Forwarded C-STORE Response: {rq.SOPInstanceUID} {rp.Status}"); - }; - await client.AddRequestAsync(forwardRequest); - await client.SendAsync(); + var findDestination = DicomSCPServiceConfig.ThirdDestinationAEList.FirstOrDefault(t => t.Name == cmoveInfo.DestinationAE); + + var forwardRequest = new DicomCStoreRequest(request.File.Clone()); + + + // 创建客户端连接到目标 PACS + var client = DicomClientFactory.Create(findDestination.IP, findDestination.Port, false, DicomSCPServiceConfig.CalledAEList.First(), cmoveInfo.DestinationAE); + + // 可以加入 OnResponseReceived 来处理目标 PACS 返回状态 + forwardRequest.OnResponseReceived += (rq, rp) => + { + Log.Logger.Information($"Forwarded C-STORE Response: {rq.SOPInstanceUID} {rp.Status}"); + + }; + + await client.AddRequestAsync(forwardRequest); + await client.SendAsync(); + } + catch (Exception ex) + { + Log.Logger.Error("Error forwarding C-STORE: " + ex.Message); + } + + + return new DicomCStoreResponse(request, DicomStatus.Success); } - catch (Exception ex) - { - Log.Logger.Error("Error forwarding C-STORE: " + ex.Message); - } - - - return new DicomCStoreResponse(request, DicomStatus.Success); } + + + #endregion //确保来了影像集合存在 diff --git a/IRC.Core.SCP/appsettings.Test_HIR_SCP.json b/IRC.Core.SCP/appsettings.Test_HIR_SCP.json index 3f5b3c0d7..a4e8f751d 100644 --- a/IRC.Core.SCP/appsettings.Test_HIR_SCP.json +++ b/IRC.Core.SCP/appsettings.Test_HIR_SCP.json @@ -25,13 +25,26 @@ }, "DicomSCPServiceConfig": { "IsSupportThirdService": true, - "ThirdSearchPacsAE": "ThirdCalledPacsAE", - "ThirdCallningAE": "ThirdCallningAE", - "ThirdIP": "192.168.3.15", - "THirdPort": 112, + + "ThirdDestinationAEList": [ + { + "Name": "LYAE", + "IP": "192.168.3.194", + "Port": "6000" + }, + { + "Name": "HIRLAE", + "IP": "106.14.89.110", + "Port": "6000" + } + ] + + + "CalledAEList": [ - "HIRAE" + "HIRAE", + "STORESCP" ], - "ServerPort": 11112 + "ServerPort": 11115 } } diff --git a/IRaCIS.Core.API/HostService/DicomSCPService.cs b/IRaCIS.Core.API/HostService/DicomSCPService.cs index 5475dd257..ba514b1e2 100644 --- a/IRaCIS.Core.API/HostService/DicomSCPService.cs +++ b/IRaCIS.Core.API/HostService/DicomSCPService.cs @@ -199,23 +199,26 @@ namespace IRaCIS.Core.API.HostService // 异步发送到真实 PACS - _ = Task.Run(async () => + + try { - try - { - var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); - await client.AddRequestAsync(forward); - await client.SendAsync(); - } - catch (Exception ex) - { - Console.WriteLine("Error forwarding C-FIND: " + ex.Message); - } - finally - { - channel.Writer.Complete(); - } - }); + var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); + await client.AddRequestAsync(forward); + await client.SendAsync(); + } + catch (Exception ex) + { + Console.WriteLine("Error forwarding C-FIND: " + ex.Message); + } + finally + { + channel.Writer.Complete(); + } + + //_ = Task.Run(async () => + //{ + + //}); // 异步 yield 返回给上游 await foreach (var resp in channel.Reader.ReadAllAsync()) @@ -263,6 +266,7 @@ namespace IRaCIS.Core.API.HostService var channel = Channel.CreateUnbounded(); var clonedDataset = request.Dataset?.Clone() ?? new DicomDataset(); + var forward = new DicomCMoveRequest(hirServer.CalledAE, studyInstanceUid) { Dataset = clonedDataset @@ -281,6 +285,9 @@ namespace IRaCIS.Core.API.HostService Completed = rp.Completed, }; + + Logger.LogInformation($"Completed:{rp.Completed}"); + channel.Writer.TryWrite(proxyResp); if (!rp.Status.Equals(DicomStatus.Pending)) @@ -290,23 +297,25 @@ namespace IRaCIS.Core.API.HostService }; // 异步发送到真实 PACS - _ = Task.Run(async () => + try { - try - { - var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); - await client.AddRequestAsync(forward); - await client.SendAsync(); - } - catch (Exception ex) - { - Console.WriteLine("Error forwarding C-MOVE: " + ex.Message); - } - finally - { - channel.Writer.Complete(); - } - }); + var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); + await client.AddRequestAsync(forward); + await client.SendAsync(); + } + catch (Exception ex) + { + Console.WriteLine("Error forwarding C-MOVE: " + ex.Message); + } + finally + { + channel.Writer.Complete(); + } + + //_ = Task.Run(async () => + //{ + + //}); // 异步 yield 回上游 await foreach (var resp in channel.Reader.ReadAllAsync()) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 5be1c39cd..0d306d9a9 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -103,6 +103,8 @@ builder.Services.AddControllers(options => }) .AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理 +builder.Services.AddOptions().Configure(_configuration.GetSection("DicomSCPServiceConfig")); + // Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码 builder.Services.AddDynamicWebApiSetup(); //MinimalAPI diff --git a/IRaCIS.Core.API/appsettings.Test_HIR.json b/IRaCIS.Core.API/appsettings.Test_HIR.json index f3447d7a3..b4c0a4447 100644 --- a/IRaCIS.Core.API/appsettings.Test_HIR.json +++ b/IRaCIS.Core.API/appsettings.Test_HIR.json @@ -56,8 +56,8 @@ }, "DicomSCPServiceConfig": { "IsSupportThirdService": true, - "ThirdSearchPacsAE": "ThirdCalledPacsAE", - "ThirdCallningAE": "ThirdCallningAE", + "ThirdSearchPacsAE": "XCPACS", + "ThirdCallningAE": "LYAE", "CalledAEList": [ "HIRSCUAE", "HIRSCPAE"