HIR 对接第三方初步测试完毕
continuous-integration/drone/push Build is passing Details

Test_HIR_Net8
hang 2025-10-28 15:08:23 +08:00
parent 064e3836ca
commit aed778bbc6
5 changed files with 144 additions and 107 deletions

View File

@ -27,6 +27,7 @@ using FellowOakDicom.Imaging.Codec;
using FellowOakDicom.IO.Buffer; using FellowOakDicom.IO.Buffer;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using FellowOakDicom.Network.Client; using FellowOakDicom.Network.Client;
using MassTransit.Futures.Contracts;
namespace IRaCIS.Core.SCP.Service namespace IRaCIS.Core.SCP.Service
{ {
@ -35,14 +36,7 @@ namespace IRaCIS.Core.SCP.Service
{ {
public bool IsSupportThirdService { get; set; } public bool IsSupportThirdService { get; set; }
public string ThirdSearchPacsAE { get; set; } public List<ThirdDestinationAE> ThirdDestinationAEList { get; set; }
public string ThirdCallningAE { get; set; }
public string ThirdIP { get; set; }
public int THirdPort { get; set; }
public List<string> CalledAEList { get; set; } public List<string> CalledAEList { get; set; }
@ -50,6 +44,15 @@ namespace IRaCIS.Core.SCP.Service
public string ServerPort { get; set; } 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}的连接"); Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
//_serviceProvider = (IServiceProvider)this.UserState;
//var tt = _injectServiceProvider;
var option = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue; var option = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue;
@ -175,23 +175,27 @@ namespace IRaCIS.Core.SCP.Service
public async Task OnReceiveAssociationReleaseRequestAsync() public async Task OnReceiveAssociationReleaseRequestAsync()
{ {
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>(); if (_isCurrentThirdForward == false)
var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}");
using (await @lock.AcquireAsync())
{ {
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
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(); await SendAssociationReleaseResponseAsync();
} }
@ -273,26 +277,28 @@ namespace IRaCIS.Core.SCP.Service
public async void OnConnectionClosed(Exception exception) public async void OnConnectionClosed(Exception exception)
{ {
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>(); if (_isCurrentThirdForward == false)
if (exception == null || _releasedNormally == false)
{ {
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭 var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true });
//记录日志 if (exception == null || _releasedNormally == false)
await AddUploadLogAsync(); {
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭
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}"); Log.Logger.Warning($"连接关闭 {_releasedNormally} {exception?.Message} {exception?.InnerException?.Message}");
} }
@ -319,46 +325,53 @@ namespace IRaCIS.Core.SCP.Service
#region 判断是否转发第三方影像 #region 判断是否转发第三方影像
var cmoveInfo = _cmoveStudyRepository.Where(t => t.StudyInstanceUIDList.Any(c => c == studyInstanceUid)).OrderByDescending(t => t.CreateTime).FirstOrDefault(); if (DicomSCPServiceConfig.IsSupportThirdService)
//确定是第三方请求
if (cmoveInfo != null && cmoveInfo.CallingAE == DicomSCPServiceConfig.ThirdCallningAE)
{ {
_isCurrentThirdForward = true; var cmoveInfo = _cmoveStudyRepository.Where(t => t.StudyInstanceUIDList.Any(c => c == studyInstanceUid)).OrderByDescending(t => t.CreateTime).FirstOrDefault();
var _dicomAERepository = _serviceProvider.GetService<IRepository<DicomAE>>(); //确定是第三方请求
var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer); if (cmoveInfo != null && DicomSCPServiceConfig.ThirdDestinationAEList.Any(t => t.Name == cmoveInfo.DestinationAE))
try
{ {
// 克隆 Dataset 避免共享引用 _isCurrentThirdForward = true;
var dsCopy = request.Dataset?.Clone();
var forwardRequest = new DicomCStoreRequest(dsCopy); var _dicomAERepository = _serviceProvider.GetService<IRepository<DicomAE>>();
var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer);
try
// 创建客户端连接到目标 PACS
var client = DicomClientFactory.Create(DicomSCPServiceConfig.ThirdIP, DicomSCPServiceConfig.THirdPort, 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); var findDestination = DicomSCPServiceConfig.ThirdDestinationAEList.FirstOrDefault(t => t.Name == cmoveInfo.DestinationAE);
await client.SendAsync();
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 #endregion
//确保来了影像集合存在 //确保来了影像集合存在

View File

@ -25,13 +25,26 @@
}, },
"DicomSCPServiceConfig": { "DicomSCPServiceConfig": {
"IsSupportThirdService": true, "IsSupportThirdService": true,
"ThirdSearchPacsAE": "ThirdCalledPacsAE",
"ThirdCallningAE": "ThirdCallningAE", "ThirdDestinationAEList": [
"ThirdIP": "192.168.3.15", {
"THirdPort": 112, "Name": "LYAE",
"IP": "192.168.3.194",
"Port": "6000"
},
{
"Name": "HIRLAE",
"IP": "106.14.89.110",
"Port": "6000"
}
]
"CalledAEList": [ "CalledAEList": [
"HIRAE" "HIRAE",
"STORESCP"
], ],
"ServerPort": 11112 "ServerPort": 11115
} }
} }

View File

@ -199,23 +199,26 @@ namespace IRaCIS.Core.API.HostService
// 异步发送到真实 PACS // 异步发送到真实 PACS
_ = Task.Run(async () =>
try
{ {
try var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE);
{ await client.AddRequestAsync(forward);
var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); await client.SendAsync();
await client.AddRequestAsync(forward); }
await client.SendAsync(); catch (Exception ex)
} {
catch (Exception ex) Console.WriteLine("Error forwarding C-FIND: " + ex.Message);
{ }
Console.WriteLine("Error forwarding C-FIND: " + ex.Message); finally
} {
finally channel.Writer.Complete();
{ }
channel.Writer.Complete();
} //_ = Task.Run(async () =>
}); //{
//});
// 异步 yield 返回给上游 // 异步 yield 返回给上游
await foreach (var resp in channel.Reader.ReadAllAsync()) await foreach (var resp in channel.Reader.ReadAllAsync())
@ -263,6 +266,7 @@ namespace IRaCIS.Core.API.HostService
var channel = Channel.CreateUnbounded<DicomCMoveResponse>(); var channel = Channel.CreateUnbounded<DicomCMoveResponse>();
var clonedDataset = request.Dataset?.Clone() ?? new DicomDataset(); var clonedDataset = request.Dataset?.Clone() ?? new DicomDataset();
var forward = new DicomCMoveRequest(hirServer.CalledAE, studyInstanceUid) var forward = new DicomCMoveRequest(hirServer.CalledAE, studyInstanceUid)
{ {
Dataset = clonedDataset Dataset = clonedDataset
@ -281,6 +285,9 @@ namespace IRaCIS.Core.API.HostService
Completed = rp.Completed, Completed = rp.Completed,
}; };
Logger.LogInformation($"Completed:{rp.Completed}");
channel.Writer.TryWrite(proxyResp); channel.Writer.TryWrite(proxyResp);
if (!rp.Status.Equals(DicomStatus.Pending)) if (!rp.Status.Equals(DicomStatus.Pending))
@ -290,23 +297,25 @@ namespace IRaCIS.Core.API.HostService
}; };
// 异步发送到真实 PACS // 异步发送到真实 PACS
_ = Task.Run(async () => try
{ {
try var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE);
{ await client.AddRequestAsync(forward);
var client = DicomClientFactory.Create(find.IP, find.Port, false, hirClient.CalledAE, find.CalledAE); await client.SendAsync();
await client.AddRequestAsync(forward); }
await client.SendAsync(); catch (Exception ex)
} {
catch (Exception ex) Console.WriteLine("Error forwarding C-MOVE: " + ex.Message);
{ }
Console.WriteLine("Error forwarding C-MOVE: " + ex.Message); finally
} {
finally channel.Writer.Complete();
{ }
channel.Writer.Complete();
} //_ = Task.Run(async () =>
}); //{
//});
// 异步 yield 回上游 // 异步 yield 回上游
await foreach (var resp in channel.Reader.ReadAllAsync()) await foreach (var resp in channel.Reader.ReadAllAsync())

View File

@ -103,6 +103,8 @@ builder.Services.AddControllers(options =>
}) })
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理 .AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
builder.Services.AddOptions().Configure<DicomSCPServiceOption>(_configuration.GetSection("DicomSCPServiceConfig"));
// Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码 // Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码
builder.Services.AddDynamicWebApiSetup(); builder.Services.AddDynamicWebApiSetup();
//MinimalAPI //MinimalAPI

View File

@ -56,8 +56,8 @@
}, },
"DicomSCPServiceConfig": { "DicomSCPServiceConfig": {
"IsSupportThirdService": true, "IsSupportThirdService": true,
"ThirdSearchPacsAE": "ThirdCalledPacsAE", "ThirdSearchPacsAE": "XCPACS",
"ThirdCallningAE": "ThirdCallningAE", "ThirdCallningAE": "LYAE",
"CalledAEList": [ "CalledAEList": [
"HIRSCUAE", "HIRSCUAE",
"HIRSCPAE" "HIRSCPAE"