From 57600cbad19b7bdba76b48409f9458a3c1715387 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 21 May 2024 14:29:13 +0800 Subject: [PATCH] =?UTF-8?q?IR=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ImageAndDoc/DTO/UnionStudyViewDodel.cs | 39 ++- .../ImageAndDoc/DownloadAndUploadService.cs | 302 +++++++++++++++++- .../Service/ImageAndDoc/StudyService.cs | 123 ++++++- .../Service/Visit/DTO/VisitPointViewModel.cs | 11 + 4 files changed, 453 insertions(+), 22 deletions(-) diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs index 87e45a870..647b1220b 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs @@ -157,19 +157,19 @@ namespace IRaCIS.Core.Application.Contracts public Guid? VisitTaskId { get; set; } - public bool? IsDicom { get; set; } + public bool? IsDicom { get; set; } - public string? Uploader { get; set; } + public string? Uploader { get; set; } - public bool? IsSuccess { get; set; } + public bool? IsSuccess { get; set; } public string? StudyCode { get; set; } - } + } public class PreArchiveDicomStudyCommand { - //public string StudyInstanceUid { get; set; } + //public string StudyInstanceUid { get; set; } [NotDefault] public Guid TrialId { get; set; } @@ -180,7 +180,7 @@ namespace IRaCIS.Core.Application.Contracts [NotDefault] public Guid SubjectVisitId { get; set; } - + public decimal FileSize { get; set; } public bool IsDicomReUpload { get; set; } @@ -189,6 +189,23 @@ namespace IRaCIS.Core.Application.Contracts public int FileCount { get; set; } } + public class TaskArchiveStudyCommand + { + [NotDefault] + public Guid TrialId { get; set; } + [NotDefault] + public Guid SubjectId { get; set; } + + [NotDefault] + public Guid StudyMonitorId { get; set; } + + public int FailedFileCount { get; set; } + + public string RecordPath { get; set; } = string.Empty; + + public AddOrUpdateStudyDto Study { get; set; } + } + public class NewArchiveStudyCommand { [NotDefault] @@ -200,23 +217,23 @@ namespace IRaCIS.Core.Application.Contracts [NotDefault] public Guid SubjectVisitId { get; set; } [NotDefault] - public Guid StudyMonitorId { get; set; } + public Guid StudyMonitorId { get; set; } public int FailedFileCount { get; set; } public string RecordPath { get; set; } = string.Empty; public AddOrUpdateStudyDto Study { get; set; } - - } + + } public class AddOrUpdateStudyDto { public string StudyId { get; set; } = string.Empty; - + //public int Code { get; set; } = 0; //public string StudyCode { get; set; } = string.Empty; @@ -271,7 +288,7 @@ namespace IRaCIS.Core.Application.Contracts public string AcquisitionNumber { get; set; } = string.Empty; public string TriggerTime { get; set; } = string.Empty; - public string ImageResizePath { get; set; }=string.Empty; + public string ImageResizePath { get; set; } = string.Empty; public List InstanceList { get; set; } public Guid? VisitTaskId { get; set; } diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index b7edc3b98..0205403b7 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -1,8 +1,16 @@ -using FellowOakDicom; +using EasyCaching.Core; +using FellowOakDicom; +using IRaCIS.Core.Application.Contracts; +using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; +using IRaCIS.Core.Domain.Share; +using IRaCIS.Core.Infrastructure; using MassTransit; +using MathNet.Numerics; +using Medallion.Threading; using Microsoft.AspNetCore.Mvc; +using OfficeOpenXml.FormulaParsing.Utilities; using System; using System.Collections.Generic; using System.IO.Compression; @@ -19,17 +27,23 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc [ApiExplorerSettings(GroupName = "Trial")] public class DownloadAndUploadService : BaseService, IDownloadAndUploadService { - + private readonly IEasyCachingProvider _provider; private readonly IRepository _systemAnonymizationRepository; private readonly IRepository _subjectVisitRepository; private readonly IOSSService _oSSService; - public DownloadAndUploadService(IRepository systemAnonymizationRepository, IRepository subjectVisitRepository, IOSSService oSSService) + private readonly IRepository _studyMonitorRepository; + private readonly IDistributedLockProvider _distributedLockProvider; + public DownloadAndUploadService(IEasyCachingProvider provider, IRepository systemAnonymizationRepository, IRepository subjectVisitRepository, IOSSService oSSService, + IRepository studyMonitorRepository, IDistributedLockProvider distributedLockProvider) { _systemAnonymizationRepository = systemAnonymizationRepository; _subjectVisitRepository = subjectVisitRepository; _oSSService = oSSService; + _studyMonitorRepository = studyMonitorRepository; + _distributedLockProvider = distributedLockProvider; + _provider = provider; } /// @@ -39,7 +53,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc /// public async Task>> GetSubjectImageUploadList(Guid subjectId) { - var query = _repository.Where(t => t.SubjectId == subjectId && t.SourceSubjectVisitId != null && t.DoctorUserId==_userInfo.Id) + var query = _repository.Where(t => t.SubjectId == subjectId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id) .Select(u => new SubjectImageUploadDTO() { SubejctId = u.SubjectId, @@ -53,13 +67,13 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc OrginalStudyList = u.SourceSubjectVisit.StudyList.Select(t => new StudyBasicInfo() { Id = t.Id, - StudyInstanceUid= t.StudyInstanceUid, - ModalityForEdit=t.ModalityForEdit, - BodyPartExamined=t.BodyPartExamined, + StudyInstanceUid = t.StudyInstanceUid, + ModalityForEdit = t.ModalityForEdit, + BodyPartExamined = t.BodyPartExamined, BodyPartForEdit = t.BodyPartForEdit, - - StudyCode=t.StudyCode, - StudyTime=t.StudyTime, + + StudyCode = t.StudyCode, + StudyTime = t.StudyTime, Description = t.Description, InstanceCount = t.InstanceCount, Modalities = t.Modalities, @@ -89,6 +103,274 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc return ResponseOutput.Ok(list); } + private void SpecialArchiveStudyDeal(TaskStudy study) + { + #region 特殊逻辑 + + + if (study.PatientBirthDate.Length == 8) + { + study.PatientBirthDate = $"{study.PatientBirthDate[0]}{study.PatientBirthDate[1]}{study.PatientBirthDate[2]}{study.PatientBirthDate[3]}-{study.PatientBirthDate[4]}{study.PatientBirthDate[5]}-{study.PatientBirthDate[6]}{study.PatientBirthDate[7]}"; + } + + var dicModalityList = _repository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList(); + + + var modality = study.Modalities; + + var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty; + + if (modality == "MR") + { + modalityForEdit = "MRI"; + } + + if (modality == "PT") + { + modalityForEdit = "PET"; + } + if (modality == "PT、CT") + { + modalityForEdit = "PET-CT"; + } + + study.ModalityForEdit = modalityForEdit; + #endregion + } + + [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] + public async Task AddOrUpdateArchiveTaskStudy(TaskArchiveStudyCommand incommand) + { + #region 获取该subject 已生成任务的访视的检查 + + var queryStudy = _repository.Where(t => t.SubjectId == incommand.SubjectId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id).Select(u => new + { + VisitTaskId = u.Id, + SourceSubjectVisitId = u.SourceSubjectVisitId, + OrginalStudyList = u.SourceSubjectVisit.StudyList.Select(t => new StudyBasicInfo() + { + Id = t.Id, + StudyInstanceUid = t.StudyInstanceUid + }).ToList(), + }); + + var studyList = await queryStudy.ToListAsync(); + + + var findOriginStudy = studyList.FirstOrDefault(c => c.OrginalStudyList.Any(t => t.StudyInstanceUid == incommand.Study.StudyInstanceUid)); + + if (findOriginStudy==null) + { + return ResponseOutput.NotOk("该检查不属于该受试者,请核查"); + } + + #endregion + + + + var modalitys = string.Empty; + + try + { + var trialId = incommand.TrialId; + + + var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == incommand.StudyMonitorId); + studyMonitor.UploadFinishedTime = DateTime.Now; + studyMonitor.ArchiveFinishedTime = DateTime.Now; + studyMonitor.FailedFileCount = incommand.FailedFileCount; + studyMonitor.IsSuccess = incommand.FailedFileCount == 0; + studyMonitor.RecordPath = incommand.RecordPath; + + //上传 + if (studyMonitor.IsDicomReUpload == false) + { + var study = _mapper.Map(incommand.Study); + + var @lock = _distributedLockProvider.CreateLock($"StudyCode"); + + using (await @lock.AcquireAsync()) + { + //查询数据库获取最大的Code 没有记录则为0 + var dbStudyCodeIntMax = _repository.Where(s => s.TrialId == trialId).Select(t => t.Code).DefaultIfEmpty().Max(); + + //获取缓存中的值 并发的时候,需要记录,已被占用的值 这样其他线程在此占用的最大的值上递增 + var cacheMaxCodeInt = _provider.Get($"{trialId}_{StaticData.CacheKey.StudyMaxCode}").Value; + + int currentNextCodeInt = cacheMaxCodeInt > dbStudyCodeIntMax ? cacheMaxCodeInt + 1 : dbStudyCodeIntMax + 1; + study.Code = currentNextCodeInt; + + study.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy)); + + _provider.Set($"{trialId}_{StaticData.CacheKey.StudyMaxCode}", study.Code, TimeSpan.FromMinutes(30)); + + } + + + study.Id = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString(),findOriginStudy.VisitTaskId.ToString()); + study.TrialId = incommand.TrialId; + //study.SiteId = incommand.SiteId; + study.SubjectId = incommand.SubjectId; + study.VisitTaskId = findOriginStudy.VisitTaskId; + //study.SubjectVisitId = incommand.SubjectVisitId; + + + //特殊处理逻辑 + study.Modalities = string.Join("、", incommand.Study.SeriesList.Select(t => t.Modality).Distinct()); + SpecialArchiveStudyDeal(study); + modalitys = study.Modalities; + + await _repository.AddAsync(study); + + + studyMonitor.StudyId = study.Id; + studyMonitor.StudyCode = study.StudyCode; + + + foreach (var seriesItem in incommand.Study.SeriesList) + { + var series = _mapper.Map(seriesItem); + + series.Id = IdentifierHelper.CreateGuid(seriesItem.StudyInstanceUid, seriesItem.SeriesInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); + series.StudyId = study.Id; + + series.TrialId = incommand.TrialId; + //series.SiteId = incommand.SiteId; + series.SubjectId = incommand.SubjectId; + //series.SubjectVisitId = incommand.SubjectVisitId; + series.VisitTaskId = findOriginStudy.VisitTaskId; + + //前端传递的数量不准,上传的时候,把失败的也加进来了,以实际数组的数字为准 + series.InstanceCount = seriesItem.InstanceList.Count; + + await _repository.AddAsync(series); + + foreach (var instanceItem in seriesItem.InstanceList) + { + var isntance = _mapper.Map(instanceItem); + + Guid instanceId = IdentifierHelper.CreateGuid(study.StudyInstanceUid, series.SeriesInstanceUid, isntance.SopInstanceUid, study.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); + + isntance.Id = instanceId; + isntance.StudyId = study.Id; + isntance.SeriesId = series.Id; + + isntance.TrialId = incommand.TrialId; + //isntance.SiteId = incommand.SiteId; + isntance.SubjectId = incommand.SubjectId; + //isntance.SubjectVisitId = incommand.SubjectVisitId; + isntance.VisitTaskId = findOriginStudy.VisitTaskId; + + await _repository.AddAsync(isntance); + } + } + + + + + } + else + { + + var studyId = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString()); ; + + var study = await _repository.Where().FirstOrDefaultAsync(t => t.Id == studyId); + + //重传的时候也要赋值检查Id + studyMonitor.StudyId = study.Id; + studyMonitor.StudyCode = study.StudyCode; + + //特殊处理逻辑 + study.Modalities = string.Join("、", incommand.Study.SeriesList.Select(t => t.Modality).Union(study.Modalities.Split("、", StringSplitOptions.RemoveEmptyEntries)).Distinct()); + SpecialArchiveStudyDeal(study); + modalitys = study.Modalities; + + + // 少了整个序列 + + //某个序列下少了instance + foreach (var seriesItem in incommand.Study.SeriesList) + { + var seriesId = IdentifierHelper.CreateGuid(seriesItem.StudyInstanceUid, seriesItem.SeriesInstanceUid, trialId.ToString(), findOriginStudy.VisitTaskId.ToString()); + + TaskSeries dicomSeries = await _repository.FirstOrDefaultAsync(t => t.Id == seriesId); + + //判断重复 + if (dicomSeries == null) + { + var series = _mapper.Map(seriesItem); + + series.Id = seriesId; + series.StudyId = study.Id; + + series.TrialId = incommand.TrialId; + //series.SiteId = incommand.SiteId; + series.SubjectId = incommand.SubjectId; + //series.SubjectVisitId = incommand.SubjectVisitId; + + + dicomSeries = await _repository.AddAsync(series); + + //新的序列 那么 检查的序列数量+1 + study.SeriesCount += 1; + } + else + { + //该序列掉了instance + dicomSeries.InstanceCount += seriesItem.InstanceList.Count; + } + + foreach (var instanceItem in seriesItem.InstanceList) + { + var insntance = _mapper.Map(instanceItem); + insntance.Id = IdentifierHelper.CreateGuid(insntance.StudyInstanceUid, insntance.SeriesInstanceUid, insntance.SopInstanceUid, trialId.ToString(), findOriginStudy.VisitTaskId.ToString()); + insntance.StudyId = study.Id; + insntance.SeriesId = dicomSeries.Id; + + insntance.TrialId = incommand.TrialId; + //insntance.SiteId = incommand.SiteId; + insntance.SubjectId = incommand.SubjectId; + insntance.VisitTaskId = findOriginStudy.VisitTaskId; + + await _repository.AddAsync(insntance); + } + + + // 不管是新的序列 还是 该序列 掉了Instance 重传的时候 检查的instance 数量都会增加 + study.InstanceCount += seriesItem.InstanceList.Count; + + } + + + } + + var @lock2 = _distributedLockProvider.CreateLock($"StudyCommit"); + + using (await @lock2.AcquireAsync()) + { + await _repository.SaveChangesAsync(); + } + } + catch (Exception ex) + { + + return ResponseOutput.NotOk(ex.Message); + } + finally + { + _provider.Remove($"StudyUid_{incommand.TrialId}_{incommand.Study.StudyInstanceUid}"); + } + + + + + + + + return ResponseOutput.Ok(modalitys); + } + + /// /// 打包和匿名化影像 默认是匿名化打包,也可以不匿名化打包 /// diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index 57e7a8cec..5af850f70 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -15,6 +15,9 @@ using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using System.Threading; using Medallion.Threading; +using System.Reactive.Subjects; +using DocumentFormat.OpenXml.Drawing.Diagrams; +using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; namespace IRaCIS.Core.Application.Service.ImageAndDoc { @@ -710,6 +713,124 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc + [HttpPost] + public async Task>> VerifyTaskStudyAllowUploadAsync(VerifyTaskUploadOrReupload verifyInfo) + { + + var queryStudy = _repository.Where(t => t.SubjectId == verifyInfo.SubjectId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id).Select(u => new + { + VisitTaskId = u.Id, + SourceSubjectVisitId = u.SourceSubjectVisitId, + OrginalStudyList = u.SourceSubjectVisit.StudyList.Select(t => new StudyBasicInfo() + { + Id = t.Id, + StudyInstanceUid = t.StudyInstanceUid + }).ToList(), + + UploadStudyList = u.TaskStudyList.Select(t => new + { + Id = t.Id, + StudyInstanceUid = t.StudyInstanceUid, + + SeriesList=t.SeriesList.Select(t => new UploadedSeries() + { SeriesId = t.Id, SeriesInstanceUid = t.SeriesInstanceUid, SOPInstanceUIDList = t.InstanceList.Select(c => c.SopInstanceUid).ToList() }).ToList() + + + }).ToList() + }); + + var studyList = await queryStudy.ToListAsync(); + + var resultList = new List(); + + var trialId = verifyInfo.TrialId; + + var subjectId = verifyInfo.SubjectId; + + var visitList = _repository.Where(t => t.SubjectId == verifyInfo.SubjectId).Select(t => new { t.VisitNum, t.EarliestScanDate, t.LatestScanDate, t.Id }).ToList(); + + foreach (var waitUploadItem in verifyInfo.StudyInstanceUidList) + { + + var studyInstanceUid = waitUploadItem.StudyInstanceUid; + + var result = new VerifyStudyUploadResult(); + + if (_provider.Exists($"StudyUid_{trialId}_{studyInstanceUid}") && _provider.Get($"StudyUid_{trialId}_{studyInstanceUid}").Value != _userInfo.Id) + { + + result.AllowUpload = false; + + result.AllowReUpload = false; + result.StudyInstanceUid = studyInstanceUid; + //---当前有人正在上传归档该检查! + result.ErrorMesseage = _localizer["Study_UploadArchiving"]; + + resultList.Add(result); + continue; + } + + + + var findOriginStudy = studyList.FirstOrDefault(c => c.OrginalStudyList.Any(t => t.StudyInstanceUid == studyInstanceUid)); + + + if (findOriginStudy == null) + { + throw new BusinessValidationFailedException("该检查不属于该受试者,请核查") ; + } + + else + { + if(findOriginStudy.UploadStudyList.Any(t=>t.StudyInstanceUid == studyInstanceUid)) + { + result.AllowReUpload = true; + result.AllowUpload = false; + + result.UploadedSeriesList = findOriginStudy.UploadStudyList.Where(t => t.StudyInstanceUid == studyInstanceUid).SelectMany(t => t.SeriesList).ToList(); + + } + else + { + result.AllowUpload = true; + + result.AllowReUpload = false; + } + } + + result.StudyInstanceUid = studyInstanceUid; + + var @lock = _distributedLockProvider.CreateLock($"StudyUpload"); + + using (@lock.Acquire()) + { + if (result.AllowReUpload || result.AllowUpload) + { + _provider.Set($"StudyUid_{trialId}_{studyInstanceUid}", _userInfo.Id, TimeSpan.FromSeconds(30)); + } + else + { + _provider.Remove($"StudyUid_{trialId}_{studyInstanceUid}"); + } + } + resultList.Add(result); + } + + + + + + var systemAnonymizationList = _repository.Where(t => t.IsEnable).ToList(); + + return ResponseOutput.Ok>(resultList, new + { + AnonymizeFixedList = systemAnonymizationList.Where(t => t.IsFixed).ToList(), + AnonymizeNotFixedList = systemAnonymizationList.Where(t => t.IsFixed == false).ToList() + }); + } + + + /// /// 批量验证 检查是否可以上传 并告知原因 /// @@ -789,7 +910,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc return result; } - if (_repository.Where(t => t.Id == SubjectId).Select(t => t.Status).FirstOrDefault() == SubjectStatus.EndOfVisit) + if (_repository.Where(t => t.Id == SubjectId).Select(t => t.Status).FirstOrDefault() == SubjectStatus.EndOfVisit) { result.AllowUpload = false; diff --git a/IRaCIS.Core.Application/Service/Visit/DTO/VisitPointViewModel.cs b/IRaCIS.Core.Application/Service/Visit/DTO/VisitPointViewModel.cs index 8ccf68a73..0a8ecb67e 100644 --- a/IRaCIS.Core.Application/Service/Visit/DTO/VisitPointViewModel.cs +++ b/IRaCIS.Core.Application/Service/Visit/DTO/VisitPointViewModel.cs @@ -394,6 +394,17 @@ namespace IRaCIS.Core.Application.Contracts } + public class VerifyTaskUploadOrReupload + { + [NotDefault] + public Guid TrialId { get; set; } + + [NotDefault] + public Guid SubjectId { get; set; } + + public List StudyInstanceUidList { get; set; } = new List(); + } + public class VerifyUploadOrReupload { [NotDefault]