对接联影,转发c-find c-move请求,并且发送c-store 请求
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
e5f6685719
commit
e496cf1117
|
|
@ -26,12 +26,25 @@ using Newtonsoft.Json;
|
||||||
using FellowOakDicom.Imaging.Codec;
|
using FellowOakDicom.Imaging.Codec;
|
||||||
using FellowOakDicom.IO.Buffer;
|
using FellowOakDicom.IO.Buffer;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using FellowOakDicom.Network.Client;
|
||||||
|
|
||||||
namespace IRaCIS.Core.SCP.Service
|
namespace IRaCIS.Core.SCP.Service
|
||||||
{
|
{
|
||||||
|
|
||||||
public class DicomSCPServiceOption
|
public class DicomSCPServiceOption
|
||||||
{
|
{
|
||||||
|
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<string> CalledAEList { get; set; }
|
public List<string> CalledAEList { get; set; }
|
||||||
|
|
||||||
public string ServerPort { get; set; }
|
public string ServerPort { get; set; }
|
||||||
|
|
@ -51,12 +64,16 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
|
|
||||||
private SCPImageUpload _upload { get; set; }
|
private SCPImageUpload _upload { get; set; }
|
||||||
|
|
||||||
|
private DicomSCPServiceOption DicomSCPServiceConfig { get; set; }
|
||||||
|
|
||||||
public HospitalGroup CurrentHospitalGroup { get; set; }
|
public HospitalGroup CurrentHospitalGroup { get; set; }
|
||||||
|
|
||||||
private List<Guid> HospitalGroupIdList { get; set; }
|
private List<Guid> HospitalGroupIdList { get; set; }
|
||||||
|
|
||||||
private bool _releasedNormally = false;
|
private bool _releasedNormally = false;
|
||||||
|
|
||||||
|
private bool _isCurrentThirdForward = false;
|
||||||
|
|
||||||
|
|
||||||
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
|
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
|
||||||
{
|
{
|
||||||
|
|
@ -97,6 +114,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
|
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
|
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -108,7 +126,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
|
|
||||||
var option = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue;
|
var option = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue;
|
||||||
|
|
||||||
|
DicomSCPServiceConfig = option;
|
||||||
|
|
||||||
var _hospitalGroupRepository = _serviceProvider.GetService<IRepository<HospitalGroup>>();
|
var _hospitalGroupRepository = _serviceProvider.GetService<IRepository<HospitalGroup>>();
|
||||||
|
|
||||||
|
|
@ -179,6 +197,9 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
|
|
||||||
|
|
||||||
private async Task AddUploadLogAsync()
|
private async Task AddUploadLogAsync()
|
||||||
|
{
|
||||||
|
//转发第三方,那么不记录日志
|
||||||
|
if (_isCurrentThirdForward == false)
|
||||||
{
|
{
|
||||||
//记录监控
|
//记录监控
|
||||||
|
|
||||||
|
|
@ -193,6 +214,8 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
await _SCPImageUploadRepository.AddAsync(_upload, _upload.FileCount > 0 ? true : false);
|
await _SCPImageUploadRepository.AddAsync(_upload, _upload.FileCount > 0 ? true : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task DataMaintenanceAsaync()
|
private async Task DataMaintenanceAsaync()
|
||||||
{
|
{
|
||||||
|
|
@ -292,6 +315,52 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
return new DicomCStoreResponse(request, DicomStatus.Success);
|
return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _cmoveStudyRepository = _serviceProvider.GetService<IRepository<CmoveStudy>>();
|
||||||
|
|
||||||
|
#region 判断是否转发第三方影像
|
||||||
|
|
||||||
|
var cmoveInfo = _cmoveStudyRepository.Where(t => t.StudyInstanceUIDList.Any(c => c == studyInstanceUid)).OrderByDescending(t => t.CreateTime).FirstOrDefault();
|
||||||
|
|
||||||
|
//确定是第三方请求
|
||||||
|
if (cmoveInfo != null && cmoveInfo.CallingAE == DicomSCPServiceConfig.ThirdCallningAE)
|
||||||
|
{
|
||||||
|
_isCurrentThirdForward = true;
|
||||||
|
|
||||||
|
var _dicomAERepository = _serviceProvider.GetService<IRepository<DicomAE>>();
|
||||||
|
var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 克隆 Dataset 避免共享引用
|
||||||
|
var dsCopy = request.Dataset?.Clone();
|
||||||
|
|
||||||
|
var forwardRequest = new DicomCStoreRequest(dsCopy);
|
||||||
|
|
||||||
|
|
||||||
|
// 创建客户端连接到目标 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);
|
||||||
|
await client.SendAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Logger.Error("Error forwarding C-STORE: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
//确保来了影像集合存在
|
//确保来了影像集合存在
|
||||||
if (!_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
if (!_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||||
{
|
{
|
||||||
|
|
@ -307,7 +376,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||||
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
||||||
var _studyGroupRepository = _serviceProvider.GetService<IRepository<SCPStudyHospitalGroup>>();
|
var _studyGroupRepository = _serviceProvider.GetService<IRepository<SCPStudyHospitalGroup>>();
|
||||||
|
|
||||||
var _cmoveStudyRepository = _serviceProvider.GetService<IRepository<CmoveStudy>>();
|
|
||||||
|
|
||||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,13 @@
|
||||||
"Hangfire": "Server=106.14.89.110,1435;Database=Test_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
"Hangfire": "Server=106.14.89.110,1435;Database=Test_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||||
},
|
},
|
||||||
"DicomSCPServiceConfig": {
|
"DicomSCPServiceConfig": {
|
||||||
|
"IsSupportThirdService": true,
|
||||||
|
"ThirdSearchPacsAE": "ThirdCalledPacsAE",
|
||||||
|
"ThirdCallningAE": "ThirdCallningAE",
|
||||||
|
"ThirdIP": "192.168.3.15",
|
||||||
|
"THirdPort": 112,
|
||||||
"CalledAEList": [
|
"CalledAEList": [
|
||||||
"STORESCP",
|
"HIRAE"
|
||||||
"HIRAE",
|
|
||||||
"Value2",
|
|
||||||
"Value3"
|
|
||||||
],
|
],
|
||||||
"ServerPort": 11112
|
"ServerPort": 11112
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,320 @@
|
||||||
|
using AutoMapper.Execution;
|
||||||
|
using FellowOakDicom;
|
||||||
|
using FellowOakDicom.Network;
|
||||||
|
using FellowOakDicom.Network.Client;
|
||||||
|
using IRaCIS.Core.Domain.Models;
|
||||||
|
using IRaCIS.Core.Infra.EFCore;
|
||||||
|
using IRaCIS.Core.Infrastructure.Extention;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Org.BouncyCastle.Bcpg;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IRaCIS.Core.API.HostService
|
||||||
|
{
|
||||||
|
public class DicomSCPServiceOption
|
||||||
|
{
|
||||||
|
public bool IsSupportThirdService { get; set; }
|
||||||
|
|
||||||
|
public string ThirdSearchPacsAE { get; set; }
|
||||||
|
|
||||||
|
public string ThirdCallningAE { get; set; }
|
||||||
|
|
||||||
|
public List<string> CalledAEList { get; set; }
|
||||||
|
|
||||||
|
public string ServerPort { get; set; }
|
||||||
|
}
|
||||||
|
public class DicomSCPService : DicomService, IDicomServiceProvider, IDicomCFindProvider, IDicomCEchoProvider, IDicomCMoveProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
|
||||||
|
{
|
||||||
|
DicomTransferSyntax.ExplicitVRLittleEndian,
|
||||||
|
DicomTransferSyntax.ExplicitVRBigEndian,
|
||||||
|
DicomTransferSyntax.ImplicitVRLittleEndian
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
|
||||||
|
{
|
||||||
|
// Lossless
|
||||||
|
DicomTransferSyntax.JPEGLSLossless,
|
||||||
|
DicomTransferSyntax.JPEG2000Lossless,
|
||||||
|
DicomTransferSyntax.JPEGProcess14SV1,
|
||||||
|
DicomTransferSyntax.JPEGProcess14,
|
||||||
|
DicomTransferSyntax.RLELossless,
|
||||||
|
|
||||||
|
// Lossy
|
||||||
|
DicomTransferSyntax.JPEGLSNearLossless,
|
||||||
|
DicomTransferSyntax.JPEG2000Lossy,
|
||||||
|
DicomTransferSyntax.JPEGProcess1,
|
||||||
|
DicomTransferSyntax.JPEGProcess2_4,
|
||||||
|
|
||||||
|
// Uncompressed
|
||||||
|
DicomTransferSyntax.ExplicitVRLittleEndian,
|
||||||
|
DicomTransferSyntax.ExplicitVRBigEndian,
|
||||||
|
DicomTransferSyntax.ImplicitVRLittleEndian
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private IServiceProvider _serviceProvider { get; set; }
|
||||||
|
|
||||||
|
private DicomSCPServiceOption DicomSCPServiceConfig { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string CallingAE { get; protected set; }
|
||||||
|
public string CalledAE { get; protected set; }
|
||||||
|
|
||||||
|
public DicomSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
|
||||||
|
: base(stream, fallbackEncoding, log, dependencies)
|
||||||
|
{
|
||||||
|
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void OnConnectionClosed(Exception exception)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Received abort from {source}, reason is {reason}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnReceiveAssociationReleaseRequestAsync()
|
||||||
|
{
|
||||||
|
return SendAssociationReleaseResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
|
||||||
|
{
|
||||||
|
CallingAE = association.CallingAE;
|
||||||
|
CalledAE = association.CalledAE;
|
||||||
|
|
||||||
|
Logger.LogInformation($"Received association request from AE: {CallingAE} with IP: {association.RemoteHost} ");
|
||||||
|
|
||||||
|
DicomSCPServiceConfig = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue;
|
||||||
|
var calledAEList = DicomSCPServiceConfig.CalledAEList;
|
||||||
|
|
||||||
|
//不支持三方服务 或者CallAE不对,那么拒绝连接
|
||||||
|
if (!calledAEList.Contains(CalledAE) || DicomSCPServiceConfig.IsSupportThirdService == false || CallingAE != DicomSCPServiceConfig.ThirdCallningAE)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Association with {CallingAE} rejected since called aet {CalledAE} is unknown");
|
||||||
|
return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceUser, DicomRejectReason.CalledAENotRecognized);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pc in association.PresentationContexts)
|
||||||
|
{
|
||||||
|
if (pc.AbstractSyntax == DicomUID.Verification
|
||||||
|
|| pc.AbstractSyntax == DicomUID.PatientRootQueryRetrieveInformationModelFind
|
||||||
|
|| pc.AbstractSyntax == DicomUID.PatientRootQueryRetrieveInformationModelMove
|
||||||
|
|| pc.AbstractSyntax == DicomUID.StudyRootQueryRetrieveInformationModelFind
|
||||||
|
|| pc.AbstractSyntax == DicomUID.StudyRootQueryRetrieveInformationModelMove)
|
||||||
|
{
|
||||||
|
pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);
|
||||||
|
}
|
||||||
|
else if (pc.AbstractSyntax == DicomUID.PatientRootQueryRetrieveInformationModelGet
|
||||||
|
|| pc.AbstractSyntax == DicomUID.StudyRootQueryRetrieveInformationModelGet)
|
||||||
|
{
|
||||||
|
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
|
||||||
|
}
|
||||||
|
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
|
||||||
|
{
|
||||||
|
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Requested abstract syntax {pc.AbstractSyntax} from {CallingAE} not supported");
|
||||||
|
pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation($"Accepted association request from {CallingAE}");
|
||||||
|
return SendAssociationAcceptAsync(association);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Received verification request from AE {0} with IP: {1}", CallingAE, Association.RemoteHost);
|
||||||
|
return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));
|
||||||
|
}
|
||||||
|
public async IAsyncEnumerable<DicomCFindResponse> OnCFindRequestAsync(DicomCFindRequest request)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Received C-FIND request, forwarding to real PACS...");
|
||||||
|
|
||||||
|
var _dicomAERepository = _serviceProvider.GetService<IRepository<DicomAE>>();
|
||||||
|
|
||||||
|
var find = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.PacsServer && t.CalledAE == DicomSCPServiceConfig.ThirdSearchPacsAE);
|
||||||
|
|
||||||
|
var hirClient = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRClient);
|
||||||
|
|
||||||
|
|
||||||
|
if (find == null || hirClient == null)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("客户端和Pacs配置未查询到");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 创建 channel 用于异步传递响应
|
||||||
|
var channel = Channel.CreateUnbounded<DicomCFindResponse>();
|
||||||
|
|
||||||
|
// 克隆 dataset 避免线程/状态冲突
|
||||||
|
var clonedDataset = request.Dataset?.Clone() ?? new DicomDataset();
|
||||||
|
var forward = new DicomCFindRequest(request.SOPClassUID, request.Level)
|
||||||
|
{
|
||||||
|
Dataset = clonedDataset
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标记是否已收到 final 状态(Success/Failure/Cancel)
|
||||||
|
var finalReceived = false;
|
||||||
|
|
||||||
|
// 当远端 PACS 返回响应时,异步写入 channel
|
||||||
|
forward.OnResponseReceived += (rq, rp) =>
|
||||||
|
{
|
||||||
|
var dsCopy = rp.Dataset?.Clone();
|
||||||
|
var proxyResp = new DicomCFindResponse(request, rp.Status)
|
||||||
|
{
|
||||||
|
Dataset = dsCopy
|
||||||
|
};
|
||||||
|
channel.Writer.TryWrite(proxyResp);
|
||||||
|
|
||||||
|
if (!rp.Status.Equals(DicomStatus.Pending))
|
||||||
|
{
|
||||||
|
finalReceived = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 异步发送到真实 PACS
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 异步 yield 返回给上游
|
||||||
|
await foreach (var resp in channel.Reader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
yield return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底:如果没有 final 响应,返回 Success
|
||||||
|
if (!finalReceived)
|
||||||
|
{
|
||||||
|
yield return new DicomCFindResponse(request, DicomStatus.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<DicomCMoveResponse> OnCMoveRequestAsync(DicomCMoveRequest request)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Received C-Move request, forwarding to real PACS...");
|
||||||
|
|
||||||
|
var _dicomAERepository = _serviceProvider.GetService<IRepository<DicomAE>>();
|
||||||
|
var _cmoveStudyRepository = _serviceProvider.GetService<IRepository<CmoveStudy>>();
|
||||||
|
|
||||||
|
var find = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.PacsServer && t.CalledAE == DicomSCPServiceConfig.ThirdSearchPacsAE);
|
||||||
|
|
||||||
|
|
||||||
|
var hirServer = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRServer);
|
||||||
|
|
||||||
|
var hirClient = await _dicomAERepository.FirstOrDefaultAsync(t => t.PacsTypeEnum == PacsType.HIRClient);
|
||||||
|
|
||||||
|
|
||||||
|
if (find == null || hirClient == null || hirServer == null)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("客户端和Pacs配置未查询到");
|
||||||
|
}
|
||||||
|
|
||||||
|
var studyInstanceUid = request.Dataset?.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
|
||||||
|
|
||||||
|
if (studyInstanceUid.IsNotNullOrEmpty())
|
||||||
|
{
|
||||||
|
await _cmoveStudyRepository.AddAsync(new CmoveStudy() { CallingAE = CallingAE, CalledAE = CalledAE, DestinationAE = request.DestinationAE, StudyInstanceUIDList = new List<string>() { studyInstanceUid }, HopitalGroupIdList = new List<Guid>() }, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var channel = Channel.CreateUnbounded<DicomCMoveResponse>();
|
||||||
|
var clonedDataset = request.Dataset?.Clone() ?? new DicomDataset();
|
||||||
|
var forward = new DicomCMoveRequest(hirServer.CalledAE, studyInstanceUid)
|
||||||
|
{
|
||||||
|
Dataset = clonedDataset
|
||||||
|
};
|
||||||
|
|
||||||
|
bool finalReceived = false;
|
||||||
|
|
||||||
|
// PACS 返回响应时写入 channel
|
||||||
|
forward.OnResponseReceived += (rq, rp) =>
|
||||||
|
{
|
||||||
|
var dsCopy = rp.Dataset?.Clone();
|
||||||
|
var proxyResp = new DicomCMoveResponse(request, rp.Status)
|
||||||
|
{
|
||||||
|
Dataset = dsCopy,
|
||||||
|
Remaining = rp.Remaining,
|
||||||
|
Completed = rp.Completed,
|
||||||
|
|
||||||
|
};
|
||||||
|
channel.Writer.TryWrite(proxyResp);
|
||||||
|
|
||||||
|
if (!rp.Status.Equals(DicomStatus.Pending))
|
||||||
|
{
|
||||||
|
finalReceived = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 异步发送到真实 PACS
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 异步 yield 回上游
|
||||||
|
await foreach (var resp in channel.Reader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
yield return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底
|
||||||
|
if (!finalReceived)
|
||||||
|
{
|
||||||
|
yield return new DicomCMoveResponse(request, DicomStatus.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using IRaCIS.Core.API;
|
using FellowOakDicom.Network;
|
||||||
|
using FellowOakDicom;
|
||||||
|
using IRaCIS.Core.API;
|
||||||
using IRaCIS.Core.API.HostService;
|
using IRaCIS.Core.API.HostService;
|
||||||
using IRaCIS.Core.Application.BusinessFilter;
|
using IRaCIS.Core.Application.BusinessFilter;
|
||||||
using IRaCIS.Core.Application.Filter;
|
using IRaCIS.Core.Application.Filter;
|
||||||
|
|
@ -126,7 +128,7 @@ builder.Services.AddhangfireSetup(_configuration);
|
||||||
|
|
||||||
|
|
||||||
//Dicom影像渲染图片 跨平台
|
//Dicom影像渲染图片 跨平台
|
||||||
builder.Services.AddDicomSetup();
|
//builder.Services.AddDicomSetup();
|
||||||
|
|
||||||
// 实时应用
|
// 实时应用
|
||||||
builder.Services.AddSignalR();
|
builder.Services.AddSignalR();
|
||||||
|
|
@ -272,6 +274,13 @@ try
|
||||||
//Log.Logger.Warning($"ContentRootPath——xx:{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}");
|
//Log.Logger.Warning($"ContentRootPath——xx:{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}");
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
DicomSetupBuilder.UseServiceProvider(app.Services);
|
||||||
|
|
||||||
|
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
|
||||||
|
|
||||||
|
var server = DicomServerFactory.Create<DicomSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services, logger: logger);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,5 +53,15 @@
|
||||||
"AuthorizationCode": "zhanying123",
|
"AuthorizationCode": "zhanying123",
|
||||||
"SiteUrl": "http://hir.test.extimaging.com/login",
|
"SiteUrl": "http://hir.test.extimaging.com/login",
|
||||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
|
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
|
||||||
|
},
|
||||||
|
"DicomSCPServiceConfig": {
|
||||||
|
"IsSupportThirdService": true,
|
||||||
|
"ThirdSearchPacsAE": "ThirdCalledPacsAE",
|
||||||
|
"ThirdCallningAE": "ThirdCallningAE",
|
||||||
|
"CalledAEList": [
|
||||||
|
"HIRSCU",
|
||||||
|
"HIRSCPAE"
|
||||||
|
],
|
||||||
|
"ServerPort": 11112
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,5 +229,9 @@ namespace IRaCIS.Core.Domain.Models
|
||||||
public List<Guid> HopitalGroupIdList { get; set; }
|
public List<Guid> HopitalGroupIdList { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[Comment("CStore-转发到目的地AE")]
|
||||||
|
public string DestinationAE { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19233
IRaCIS.Core.Infra.EFCore/Migrations/20251027063221_cstoreAddFiled.Designer.cs
generated
Normal file
19233
IRaCIS.Core.Infra.EFCore/Migrations/20251027063221_cstoreAddFiled.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,31 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace IRaCIS.Core.Infra.EFCore.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class cstoreAddFiled : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "DestinationAE",
|
||||||
|
table: "CmoveStudy",
|
||||||
|
type: "nvarchar(400)",
|
||||||
|
maxLength: 400,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "",
|
||||||
|
comment: "CStore-转发到目的地AE");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "DestinationAE",
|
||||||
|
table: "CmoveStudy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -514,6 +514,12 @@ namespace IRaCIS.Core.Infra.EFCore.Migrations
|
||||||
b.Property<Guid>("CreateUserId")
|
b.Property<Guid>("CreateUserId")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationAE")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(400)
|
||||||
|
.HasColumnType("nvarchar(400)")
|
||||||
|
.HasComment("CStore-转发到目的地AE");
|
||||||
|
|
||||||
b.Property<string>("HopitalGroupIdList")
|
b.Property<string>("HopitalGroupIdList")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue