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
{
///
/// Study
///
[Route("study")]
[ApiController, Authorize, ApiExplorerSettings(GroupName = "Image")]
public class StudyController : ControllerBase
{
private readonly IStudyService _studyService;
private readonly IDicomArchiveService _dicomArchiveService;
private readonly ILogger _logger;
private IEasyCachingProvider _provider;
private IUserInfo _userInfo;
private static object _locker = new object();
public StudyController(IStudyService studyService,
IDicomArchiveService dicomArchiveService,
ILogger logger,
IEasyCachingProvider provider, IUserInfo userInfo
)
{
_userInfo = userInfo;
_provider = provider;
_studyService = studyService;
_dicomArchiveService = dicomArchiveService;
_logger = logger;
}
/// 归档
[HttpPost, Route("archiveStudy/{trialId:guid}")]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task 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();
var seriesInstanceUidList = new List();
var instanceUidList = new List();
//重传的时候,找出当前检查已经上传的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);
//}
///// 指定资源Id,获取Dicom检查信息
///// Dicom检查的Id
//[HttpGet, Route("item/{studyId:guid}")]
//[Obsolete]
//public IResponseOutput 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> GetStudyList(StudyQueryDTO queryDto)
//{
// return ResponseOutput.Ok(_studyService.GetStudyList(queryDto));
//}
/////// 指定资源Id,渲染Dicom检查的Jpeg预览图像
/////// Dicom检查的Id
////[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");
//// }
////}
/////
///// Dicom匿名化
/////
///// 需要匿名化的检查Id
/////
//[HttpPost, Route("dicomAnonymize/{studyId:guid}/{trialId:guid}")]
//[TrialAudit(AuditType.StudyAudit, AuditOptType.Anonymized)]
//[Obsolete]
//[TypeFilter(typeof(TrialResourceFilter))]
//public async Task DicomAnonymize(Guid studyId)
//{
// string userName = User.FindFirst("realName").Value; ;
// return await _studyService.DicomAnonymize(studyId, userName);
//}
/////
///// 获取受试者 这次访视 对应的study modality 列表
/////
/////
/////
/////
/////
/////
//[HttpPost, Route("getSubjectVisitStudyList/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{subjectVisitId:guid}")]
//[Obsolete]
//[AllowAnonymous]
//public IResponseOutput> GetSubjectVisitStudyList(Guid trialId, Guid siteId, Guid subjectId,
// Guid subjectVisitId)
//{
// return ResponseOutput.Ok(_studyService.GetSubjectVisitStudyList(trialId, siteId, subjectId, subjectVisitId));
//}
//[HttpPost, Route("getDistributeStudyList")]
//[Obsolete]
//public IResponseOutput> GetDistributeStudyList(StudyStatusQueryDTO studyStatusQueryDto)
//{
// return ResponseOutput.Ok(_studyService.GetDistributeStudyList(studyStatusQueryDto));
//}
/////// 删除检查
////[HttpDelete, Route("deleteStudy/{id:guid}/{trialId:guid}")]
////
////[TypeFilter(typeof(TrialResourceFilter))]
////public IResponseOutput DeleteStudy(Guid id)
////{
//// return _studyService.DeleteStudy(id);
////}
///// 更新Study状态,并保存状态变更信息
/////
//[HttpPost, Route("updateStudyStatus/{trialId:guid}")]
//[TrialAudit(AuditType.StudyAudit, AuditOptType.ChangeStudyStatus)]
//[Obsolete]
//[TypeFilter(typeof(TrialResourceFilter))]
//public IResponseOutput UpdateStudyStatus(StudyStatusDetailCommand studyStatusDetailCommand)
//{
// return _studyService.UpdateStudyStatus(studyStatusDetailCommand);
//}
/////
///// 根据项目Id 获取可选医生列表
/////
/////
/////
//[HttpGet, Route("GetReviewerList/{trialId:guid}")]
//[Obsolete]
//public IResponseOutput> GetReviewerListByTrialId(Guid trialId)
//{
// var result = _studyService.GetReviewerListByTrialId(trialId);
// return ResponseOutput.Ok(result);
//}
/////
///// 根据StudyId获取该Study的操作记录,时间倒序
/////
/////
/////
//[HttpGet, Route("getStudyStatusDetailList/{studyId:guid}")]
//[Obsolete]
//public IResponseOutput> GetStudyStatusDetailList(Guid studyId)
//{
// var result = _studyService.GetStudyStatusDetailList(studyId);
// return ResponseOutput.Ok(result);
//}
/////
///// 获取某个访视的关联访视
///// 用于获取关联影像(调用之前的接口:/series/list/,根据StudyId,获取访视的序列列表)
/////
/////
/////
/////
//[HttpGet, Route("getRelationVisitList/{visitNum}/{tpCode}")]
//[Obsolete]
//[AllowAnonymous]
//public IResponseOutput> GetRelationVisitList(decimal visitNum, string tpCode)
//{
// return ResponseOutput.Ok(_studyService.GetRelationVisitList(visitNum, tpCode));
//}
/////
///// 保存标记(跟删除合并,每次保存最新的标记),会删除替换之前的标记
///// 外层的TPcode 必须传,里面的标记数组可为空数组,表示删除该Study的所有标记
/////
/////
/////
//[HttpPost, Route("saveImageLabelList")]
//[AllowAnonymous]
//[Obsolete]
//public IResponseOutput SaveImageLabelList(ImageLabelCommand imageLabelCommand)
//{
// return ResponseOutput.Result(_studyService.SaveImageLabelList(imageLabelCommand));
//}
/////
///// 根据TPCode 获取所有的标记
/////
/////
/////
//[HttpGet, Route("getImageLabelList/{tpCode}")]
//[Obsolete]
//[AllowAnonymous]
//public IResponseOutput> GetImageLabelList(string tpCode)
//{
// return ResponseOutput.Ok(_studyService.GetImageLabel(tpCode));
//}
#endregion
}
}