419 lines
16 KiB
C#
419 lines
16 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;
|
||
|
||
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 IInspectionService _inspectionService;
|
||
private IEasyCachingProvider _provider;
|
||
private IUserInfo _userInfo;
|
||
private static object _locker = new object();
|
||
|
||
|
||
public StudyController(IStudyService studyService,
|
||
IDicomArchiveService dicomArchiveService,
|
||
ILogger<StudyController> logger,
|
||
IInspectionService inspectionService,
|
||
IEasyCachingProvider provider, IUserInfo userInfo
|
||
)
|
||
{
|
||
_userInfo = userInfo;
|
||
_provider = provider;
|
||
_studyService = studyService;
|
||
_dicomArchiveService = dicomArchiveService;
|
||
_logger = logger;
|
||
this._inspectionService = inspectionService;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// <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);
|
||
}
|
||
|
||
await _inspectionService.AddInspectionRecordAsync(
|
||
JsonConvert.DeserializeObject<DataInspectionAddDTO>(archiveStudyCommand.AuditInfo));
|
||
|
||
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
|
||
|
||
|
||
|
||
|
||
}
|
||
}
|