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 } }