EI-Image-Viewer-Api/IRaCIS.Core.API/Controllers/StudyController.cs

409 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
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 IEasyCachingProvider _provider;
private IUserInfo _userInfo;
private static object _locker = new object();
public StudyController(IStudyService studyService,
IDicomArchiveService dicomArchiveService,
ILogger<StudyController> logger,
IEasyCachingProvider provider, IUserInfo userInfo
)
{
_userInfo = userInfo;
_provider = provider;
_studyService = studyService;
_dicomArchiveService = dicomArchiveService;
_logger = logger;
}
/// <summary> 归档</summary>
[HttpPost, Route("archiveStudy/{trialId:guid}")]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> ArchiveStudy([FromForm] ArchiveStudyCommand archiveStudyCommand)
{
//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);
if (!archivedStudyIds.Contains(archiveStudyId))
archivedStudyIds.Add(archiveStudyId);
}
}
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);
if (!archivedStudyIds.Contains(archiveStudyId))
archivedStudyIds.Add(archiveStudyId);
}
}
}
catch (Exception e)
{
_logger.LogError(e.Message + e.StackTrace);
archiveResult.ErrorFiles.Add(fileName);
_provider.Remove("StudyUid_" + archiveStudyCommand.StudyInstanceUid);
}
}
section = await reader.ReadNextSectionAsync();
}
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,
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);
}
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
}
}