445 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
| using IRaCIS.Application.Interfaces;
 | ||
| using Microsoft.AspNetCore.Authorization;
 | ||
| using Microsoft.AspNetCore.Mvc;
 | ||
| using System;
 | ||
| using System.Collections.Generic;
 | ||
| using IRaCIS.Core.Application.Contracts.Dicom.DTO;
 | ||
| using Microsoft.Net.Http.Headers;
 | ||
| using Microsoft.AspNetCore.WebUtilities;
 | ||
| using System.Threading.Tasks;
 | ||
| using IRaCIS.Core.Application.Contracts.Dicom;
 | ||
| using System.IO;
 | ||
| using System.IO.Compression;
 | ||
| using IRaCIS.Core.Application.Dicom;
 | ||
| using Microsoft.Extensions.Logging;
 | ||
| using IRaCIS.Core.Application.Filter;
 | ||
| using IRaCIS.Core.Infrastructure.Extention;
 | ||
| using EasyCaching.Core;
 | ||
| using IRaCIS.Core.Infra.EFCore;
 | ||
| using IRaCIS.Core.Application.Contracts;
 | ||
| using IRaCIS.Core.Domain.Models;
 | ||
| using IRaCIS.Core.Application.Service.Inspection.Interface;
 | ||
| using Newtonsoft.Json;
 | ||
| using IRaCIS.Core.Application.Service.Inspection.DTO;
 | ||
| using IRaCIS.Core.Domain.Share;
 | ||
| using IRaCIS.Core.Domain.Common;
 | ||
| 
 | ||
| namespace IRaCIS.Api.Controllers
 | ||
| {
 | ||
|     /// <summary>
 | ||
|     /// Study
 | ||
|     /// </summary>
 | ||
|     [Route("study")]
 | ||
|     [ApiController, Authorize, ApiExplorerSettings(GroupName = "Image")]
 | ||
|     public class StudyController : ControllerBase
 | ||
|     {
 | ||
|         private readonly IStudyService _studyService;
 | ||
|         private readonly IDicomArchiveService _dicomArchiveService;
 | ||
|         private readonly ILogger<StudyController> _logger;
 | ||
|         private readonly IDictionaryService _dictionaryService;
 | ||
|         private readonly IInspectionService _inspectionService;
 | ||
|         private IEasyCachingProvider _provider;
 | ||
|         private IUserInfo _userInfo;
 | ||
|         private static object _locker = new object();
 | ||
| 
 | ||
| 
 | ||
|         public StudyController(IStudyService studyService,
 | ||
|             IDicomArchiveService dicomArchiveService,
 | ||
|             ILogger<StudyController> logger,
 | ||
|                 IDictionaryService dictionaryService,
 | ||
|               IInspectionService inspectionService,
 | ||
|             IEasyCachingProvider provider, IUserInfo userInfo
 | ||
|             )
 | ||
|         {
 | ||
|             _userInfo = userInfo;
 | ||
|             _provider = provider;
 | ||
|             _studyService = studyService;
 | ||
|             _dicomArchiveService = dicomArchiveService;
 | ||
|             _logger = logger;
 | ||
|             this._dictionaryService = dictionaryService;
 | ||
|             this._inspectionService = inspectionService;
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|         /// <summary> 归档</summary>
 | ||
|         [HttpPost, Route("archiveStudy/{trialId:guid}")]
 | ||
|         [DisableFormValueModelBinding]
 | ||
|         [DisableRequestSizeLimit]
 | ||
|         [TypeFilter(typeof(TrialResourceFilter))]
 | ||
|         public async Task<IResponseOutput> ArchiveStudy([FromForm] ArchiveStudyCommand archiveStudyCommand)
 | ||
|         {
 | ||
| 
 | ||
|             DataInspectionAddDTO data = JsonConvert.DeserializeObject<DataInspectionAddDTO>(archiveStudyCommand.AuditInfo);
 | ||
|             string studycode = string.Empty;
 | ||
|             data.CreateTime = DateTime.Now;
 | ||
|             //Stopwatch sw = new Stopwatch();
 | ||
|             var startTime = DateTime.Now;
 | ||
|             //sw.Start();
 | ||
| 
 | ||
|             if (_provider.Exists("StudyUid_" + archiveStudyCommand.StudyInstanceUid))
 | ||
|             {
 | ||
|                 return ResponseOutput.NotOk("当前已有人正在上传和归档该检查!");
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 _provider.Set("StudyUid_" + archiveStudyCommand.StudyInstanceUid, _userInfo.Id, TimeSpan.FromMinutes(30));
 | ||
|             }
 | ||
| 
 | ||
|             var archiveResult = new DicomArchiveResult();
 | ||
|             var archivedStudyIds = new List<Guid>();
 | ||
|             var seriesInstanceUidList = new List<string>();
 | ||
|             var instanceUidList = new List<string>();
 | ||
| 
 | ||
|             //重传的时候,找出当前检查已经上传的series instance
 | ||
|             if (archiveStudyCommand.AbandonStudyId != null)
 | ||
|             {
 | ||
|                 _studyService.GetHasUploadSeriesAndInstance(archiveStudyCommand.AbandonStudyId.Value, ref seriesInstanceUidList, ref instanceUidList);
 | ||
|             }
 | ||
| 
 | ||
|             var savedInfo = _studyService.GetSaveToDicomInfo(archiveStudyCommand.SubjectVisitId);
 | ||
| 
 | ||
| 
 | ||
|             var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
 | ||
|             var reader = new MultipartReader(boundary, HttpContext.Request.Body);
 | ||
|             var section = await reader.ReadNextSectionAsync();
 | ||
|             while (section != null)
 | ||
|             {
 | ||
|                 //采用post方式  这里多加一个判断   过滤其他参数   
 | ||
|                 if (string.IsNullOrEmpty(section.ContentType))
 | ||
|                 {
 | ||
|                     section = await reader.ReadNextSectionAsync();
 | ||
|                     continue;
 | ||
|                 }
 | ||
| 
 | ||
|                 var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
 | ||
|                 if (hasContentDispositionHeader)
 | ||
|                 {
 | ||
|                     string fileName = contentDisposition.FileName.Value;
 | ||
|                     try
 | ||
|                     {
 | ||
| 
 | ||
|                         string mediaType = section.ContentType;
 | ||
| 
 | ||
|                         if (mediaType.Contains("zip"))
 | ||
|                         {
 | ||
|                             var partStream = section.Body;
 | ||
|                             using (var zipArchive = new ZipArchive(partStream, ZipArchiveMode.Read))
 | ||
|                             {
 | ||
|                                 foreach (var entry in zipArchive.Entries)
 | ||
|                                 {
 | ||
|                                     if (entry.FullName.EndsWith("/")) continue;
 | ||
|                                     try
 | ||
|                                     {
 | ||
|                                         ++archiveResult.ReceivedFileCount;
 | ||
|                                         using (var memoryStream = new MemoryStream())
 | ||
|                                         {
 | ||
|                                             await section.Body.CopyToAsync(memoryStream);
 | ||
| 
 | ||
|                                             memoryStream.Seek(0, SeekOrigin.Begin);
 | ||
| 
 | ||
|                                             var archiveStudyId = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo, seriesInstanceUidList, instanceUidList);
 | ||
|                                             studycode = archiveStudyId.Item2;
 | ||
|                                             if (!archivedStudyIds.Contains(archiveStudyId.Item1))
 | ||
|                                                 archivedStudyIds.Add(archiveStudyId.Item1);
 | ||
|                                         }
 | ||
|                                     }
 | ||
|                                     catch
 | ||
|                                     {
 | ||
|                                         archiveResult.ErrorFiles.Add($"{fileName}/{entry.FullName}");
 | ||
|                                     }
 | ||
|                                 }
 | ||
|                             }
 | ||
|                         }
 | ||
| 
 | ||
|                         ++archiveResult.ReceivedFileCount;
 | ||
| 
 | ||
| 
 | ||
|                         if (mediaType.Contains("octet-stream"))
 | ||
|                         {
 | ||
|                             using (var memoryStream = new MemoryStream())
 | ||
|                             {
 | ||
|                                 await section.Body.CopyToAsync(memoryStream);
 | ||
| 
 | ||
|                                 memoryStream.Seek(0, SeekOrigin.Begin);
 | ||
| 
 | ||
|                                 var archiveStudyId = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo, seriesInstanceUidList, instanceUidList);
 | ||
|                                 studycode = archiveStudyId.Item2;
 | ||
| 
 | ||
| 
 | ||
|                                 if (!archivedStudyIds.Contains(archiveStudyId.Item1))
 | ||
|                                     archivedStudyIds.Add(archiveStudyId.Item1);
 | ||
|                             }
 | ||
|                         }
 | ||
|                     }
 | ||
|                     catch (Exception e)
 | ||
|                     {
 | ||
|                         _logger.LogError(e.Message + e.StackTrace);
 | ||
| 
 | ||
|                         archiveResult.ErrorFiles.Add(fileName);
 | ||
| 
 | ||
|                         _provider.Remove("StudyUid_" + archiveStudyCommand.StudyInstanceUid);
 | ||
| 
 | ||
|                     }
 | ||
|                 }
 | ||
|                 section = await reader.ReadNextSectionAsync();
 | ||
|             }
 | ||
| 
 | ||
|             try
 | ||
|             {
 | ||
|                 if (archivedStudyIds.Count > 0) // 上传成功,处理逻辑
 | ||
|                 {
 | ||
| 
 | ||
| 
 | ||
|                     // 同一个访视 多个线程上传处理  批量保存 可能造成死锁 https://www.cnblogs.com/johnblogs/p/9945767.html
 | ||
| 
 | ||
|                     await _dicomArchiveService.DicomDBDataSaveChange();
 | ||
| 
 | ||
|                     //sw.Stop();
 | ||
|                     _studyService.UploadOrReUploadNeedTodo(archiveStudyCommand, archivedStudyIds, ref archiveResult, new StudyMonitor()
 | ||
|                     {
 | ||
|                         TrialId = savedInfo.TrialId,
 | ||
|                         SiteId = savedInfo.SiteId,
 | ||
|                         SubjectId = savedInfo.SubjectId,
 | ||
|                         SubjectVisitId = savedInfo.SubjectVisitId,
 | ||
|                         StudyId = archivedStudyIds[0],
 | ||
|                         UploadStartTime = startTime,
 | ||
|                         UploadFinishedTime = DateTime.Now,
 | ||
|                         //TotalMillisecondsInterval = (DateTime.Now- startTime).TotalMilliseconds,
 | ||
|                         FileSize = (decimal)HttpContext.Request.ContentLength,
 | ||
|                         FileCount = archiveResult.ReceivedFileCount,
 | ||
|                         IsDicom = true,
 | ||
|                         IsDicomReUpload = archiveStudyCommand.AbandonStudyId != null,
 | ||
|                         IP = _userInfo.IP
 | ||
|                     });
 | ||
| 
 | ||
|                     _provider.Remove("StudyUid_" + archiveStudyCommand.StudyInstanceUid);
 | ||
| 
 | ||
|                 }
 | ||
|                 else
 | ||
|                 {
 | ||
|                     return ResponseOutput.NotOk("未完成该检查的归档", archiveResult);
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|                 data.GeneralId = archivedStudyIds[0];
 | ||
|                 Dictionary<string, object> keyValuePairs = new Dictionary<string, object>();
 | ||
|                 keyValuePairs.Add("StyudCode", studycode);
 | ||
|                 data.JsonDetail = _inspectionService.AddJsonItem(data.JsonDetail, keyValuePairs);
 | ||
|                 await _inspectionService.AddInspectionRecordAsync(data);
 | ||
|             }
 | ||
|             catch (Exception)
 | ||
|             {
 | ||
| 
 | ||
|                 section = await reader.ReadNextSectionAsync();
 | ||
|             }
 | ||
|          
 | ||
| 
 | ||
| 
 | ||
|          
 | ||
| 
 | ||
|             return ResponseOutput.Ok(archiveResult);
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|         #region   2021.12.14 整理废弃
 | ||
| 
 | ||
|         //[Obsolete]
 | ||
|         //[HttpGet, Route("forwardStudy/{studyId:guid}/{trialId:guid}")]
 | ||
|         //[TrialAudit(AuditType.StudyAudit, AuditOptType.Forwarded)]
 | ||
|         //[TypeFilter(typeof(TrialResourceFilter))]
 | ||
| 
 | ||
|         //public IResponseOutput ForwardStudy(Guid studyId)
 | ||
|         //{
 | ||
|         //    return _studyService.ForwardStudy(studyId);
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary> 指定资源Id,获取Dicom检查信息 </summary>
 | ||
|         ///// <param name="studyId"> Dicom检查的Id </param>
 | ||
|         //[HttpGet, Route("item/{studyId:guid}")]
 | ||
|         //[Obsolete]
 | ||
|         //public IResponseOutput<DicomStudyDTO> GetStudyItem(Guid studyId)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetStudyItem(studyId));
 | ||
|         //}
 | ||
| 
 | ||
|         //[Obsolete]
 | ||
|         //[HttpDelete, Route("deleteStudy/{id:guid}/{trialId:guid}")]
 | ||
|         //public IResponseOutput DeleteStudy(Guid id)
 | ||
|         //{
 | ||
|         //    return _studyService.DeleteStudy(id);
 | ||
|         //}
 | ||
| 
 | ||
|         //[Obsolete]
 | ||
|         //[HttpPost, Route("getStudyList")]
 | ||
|         //public IResponseOutput<PageOutput<StudyDTO>> GetStudyList(StudyQueryDTO queryDto)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetStudyList(queryDto));
 | ||
|         //}
 | ||
| 
 | ||
| 
 | ||
|         /////// <summary> 指定资源Id,渲染Dicom检查的Jpeg预览图像  </summary>
 | ||
|         /////// <param name="studyId"> Dicom检查的Id </param>
 | ||
|         ////[HttpGet, Route("preview/{studyId:guid}")]
 | ||
|         ////[AllowAnonymous]
 | ||
|         ////public FileContentResult GetStudyPreview(Guid studyId)
 | ||
|         ////{
 | ||
|         ////    string path = _studyService.GetStudyPreview(studyId);
 | ||
|         ////    using (var sw = DicomRenderingHelper.RenderPreviewJpeg(path))
 | ||
|         ////    {
 | ||
|         ////        var bytes = new byte[sw.Length];
 | ||
|         ////        sw.Read(bytes, 0, bytes.Length);
 | ||
|         ////        sw.Close();
 | ||
|         ////        return new FileContentResult(bytes, "image/jpeg");
 | ||
|         ////    }
 | ||
|         ////}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// Dicom匿名化
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="studyId">需要匿名化的检查Id</param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpPost, Route("dicomAnonymize/{studyId:guid}/{trialId:guid}")]
 | ||
|         //[TrialAudit(AuditType.StudyAudit, AuditOptType.Anonymized)]
 | ||
|         //[Obsolete]
 | ||
|         //[TypeFilter(typeof(TrialResourceFilter))]
 | ||
|         //public async Task<IResponseOutput> DicomAnonymize(Guid studyId)
 | ||
|         //{
 | ||
|         //    string userName = User.FindFirst("realName").Value; ;
 | ||
|         //    return await _studyService.DicomAnonymize(studyId, userName);
 | ||
|         //}
 | ||
| 
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 获取受试者 这次访视 对应的study  modality 列表
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="trialId"></param>
 | ||
|         ///// <param name="siteId"></param>
 | ||
|         ///// <param name="subjectId"></param>
 | ||
|         ///// <param name="subjectVisitId"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpPost, Route("getSubjectVisitStudyList/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{subjectVisitId:guid}")]
 | ||
|         //[Obsolete]
 | ||
|         //[AllowAnonymous]
 | ||
|         //public IResponseOutput<List<SubjectVisitStudyDTO>> GetSubjectVisitStudyList(Guid trialId, Guid siteId, Guid subjectId,
 | ||
|         //    Guid subjectVisitId)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetSubjectVisitStudyList(trialId, siteId, subjectId, subjectVisitId));
 | ||
|         //}
 | ||
| 
 | ||
|         //[HttpPost, Route("getDistributeStudyList")]
 | ||
|         //[Obsolete]
 | ||
|         //public IResponseOutput<PageOutput<DistributeReviewerStudyStatusDTO>> GetDistributeStudyList(StudyStatusQueryDTO studyStatusQueryDto)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetDistributeStudyList(studyStatusQueryDto));
 | ||
|         //}
 | ||
| 
 | ||
|         /////// <summary> 删除检查</summary>
 | ||
|         ////[HttpDelete, Route("deleteStudy/{id:guid}/{trialId:guid}")]
 | ||
|         ////
 | ||
|         ////[TypeFilter(typeof(TrialResourceFilter))]
 | ||
|         ////public IResponseOutput DeleteStudy(Guid id)
 | ||
|         ////{
 | ||
|         ////    return _studyService.DeleteStudy(id);
 | ||
|         ////}
 | ||
| 
 | ||
|         ///// <summary> 更新Study状态,并保存状态变更信息 </summary>
 | ||
|         ///// <param name="studyStatusDetailCommand"></param>
 | ||
| 
 | ||
|         //[HttpPost, Route("updateStudyStatus/{trialId:guid}")]
 | ||
| 
 | ||
|         //[TrialAudit(AuditType.StudyAudit, AuditOptType.ChangeStudyStatus)]
 | ||
|         //[Obsolete]
 | ||
|         //[TypeFilter(typeof(TrialResourceFilter))]
 | ||
|         //public IResponseOutput UpdateStudyStatus(StudyStatusDetailCommand studyStatusDetailCommand)
 | ||
|         //{
 | ||
|         //    return _studyService.UpdateStudyStatus(studyStatusDetailCommand);
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 根据项目Id 获取可选医生列表
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="trialId"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpGet, Route("GetReviewerList/{trialId:guid}")]
 | ||
|         //[Obsolete]
 | ||
|         //public IResponseOutput<List<ReviewerDistributionDTO>> GetReviewerListByTrialId(Guid trialId)
 | ||
|         //{
 | ||
|         //    var result = _studyService.GetReviewerListByTrialId(trialId);
 | ||
|         //    return ResponseOutput.Ok(result);
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 根据StudyId获取该Study的操作记录,时间倒序
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="studyId"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpGet, Route("getStudyStatusDetailList/{studyId:guid}")]
 | ||
|         //[Obsolete]
 | ||
|         //public IResponseOutput<List<StudyStatusDetailDTO>> GetStudyStatusDetailList(Guid studyId)
 | ||
|         //{
 | ||
|         //    var result = _studyService.GetStudyStatusDetailList(studyId);
 | ||
|         //    return ResponseOutput.Ok(result);
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 获取某个访视的关联访视
 | ||
|         ///// 用于获取关联影像(调用之前的接口:/series/list/,根据StudyId,获取访视的序列列表)
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="visitNum"></param>
 | ||
|         ///// <param name="tpCode"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpGet, Route("getRelationVisitList/{visitNum}/{tpCode}")]
 | ||
|         //[Obsolete]
 | ||
|         //[AllowAnonymous]
 | ||
|         //public IResponseOutput<IEnumerable<RelationVisitDTO>> GetRelationVisitList(decimal visitNum, string tpCode)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetRelationVisitList(visitNum, tpCode));
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 保存标记(跟删除合并,每次保存最新的标记),会删除替换之前的标记
 | ||
|         ///// 外层的TPcode 必须传,里面的标记数组可为空数组,表示删除该Study的所有标记
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="imageLabelCommand"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpPost, Route("saveImageLabelList")]
 | ||
|         //[AllowAnonymous]
 | ||
|         //[Obsolete]
 | ||
| 
 | ||
|         //public IResponseOutput SaveImageLabelList(ImageLabelCommand imageLabelCommand)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Result(_studyService.SaveImageLabelList(imageLabelCommand));
 | ||
|         //}
 | ||
| 
 | ||
|         ///// <summary>
 | ||
|         ///// 根据TPCode 获取所有的标记
 | ||
|         ///// </summary>
 | ||
|         ///// <param name="tpCode"></param>
 | ||
|         ///// <returns></returns>
 | ||
|         //[HttpGet, Route("getImageLabelList/{tpCode}")]
 | ||
|         //[Obsolete]
 | ||
|         //[AllowAnonymous]
 | ||
|         //public IResponseOutput<IEnumerable<ImageLabelDTO>> GetImageLabelList(string tpCode)
 | ||
|         //{
 | ||
|         //    return ResponseOutput.Ok(_studyService.GetImageLabel(tpCode));
 | ||
|         //}
 | ||
| 
 | ||
|         #endregion
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     }
 | ||
| }
 |