433 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			17 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 Microsoft.Extensions.Logging;
 | 
						||
using IRaCIS.Core.Application.Filter;
 | 
						||
using IRaCIS.Core.Infrastructure.Extention;
 | 
						||
using EasyCaching.Core;
 | 
						||
using IRaCIS.Core.Application.Contracts;
 | 
						||
using IRaCIS.Core.Domain.Models;
 | 
						||
using IRaCIS.Core.Application.Service.Inspection.Interface;
 | 
						||
using IRaCIS.Core.Domain.Share;
 | 
						||
 | 
						||
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)
 | 
						||
        {
 | 
						||
 | 
						||
            string studycode = string.Empty;
 | 
						||
            var startTime = DateTime.Now;
 | 
						||
 | 
						||
            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],
 | 
						||
 | 
						||
                        StudyCode= studycode,
 | 
						||
                        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("未完成该检查的归档");
 | 
						||
                }
 | 
						||
 | 
						||
 | 
						||
            }
 | 
						||
            catch (Exception e)
 | 
						||
            {
 | 
						||
 | 
						||
                _logger.LogError(e.Message + e.StackTrace);
 | 
						||
 | 
						||
                _provider.Remove("StudyUid_" + archiveStudyCommand.StudyInstanceUid);
 | 
						||
 | 
						||
                return ResponseOutput.NotOk(e.Message, ApiResponseCodeEnum.ProgramException);
 | 
						||
 | 
						||
            }
 | 
						||
             
 | 
						||
            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
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    }
 | 
						||
}
 |