using AutoMapper; using DocumentFormat.OpenXml.Drawing; using EasyCaching.Core; using ExcelDataReader; using IRaCIS.Application.Contracts; using IRaCIS.Application.Interfaces; using IRaCIS.Core.Application.Auth; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts.Dicom; using IRaCIS.Core.Application.Contracts.Dicom.DTO; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.MediatR.CommandAndQueries; using IRaCIS.Core.Application.MediatR.Handlers; using IRaCIS.Core.Application.Service; using IRaCIS.Core.Application.Service.ImageAndDoc; using IRaCIS.Core.Application.Service.Reading.Dto; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure.Extention; using Magicodes.ExporterAndImporter.Excel; using MassTransit; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using MiniExcelLibs; using Newtonsoft.Json; using SharpCompress.Archives; using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; using static IRaCIS.Core.Domain.Share.StaticData; using Path = System.IO.Path; namespace IRaCIS.Core.API.Controllers { #region 上传基类封装 [DisableFormValueModelBinding] public abstract class UploadBaseController : ControllerBase { /// 流式上传 直接返回 [Route("base")] public virtual async Task SingleFileUploadAsync(Func filePathFunc) { 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) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { var (serverFilePath, relativePath) = filePathFunc(contentDisposition.FileName.Value); await FileStoreHelper.WriteFileAsync(section.Body, serverFilePath); //仅仅返回一个文件,如果多文件上传 在最后返回多个路径 return ResponseOutput.Ok(new { FilePath = relativePath, FullFilePath = relativePath /*+ "?access_token=" + _userInfo.UserToken*/ }); } section = await reader.ReadNextSectionAsync(); } return ResponseOutput.Ok(); } /// 流式上传 通用封装 不返回任何数据,后续还有事情处理 [Route("base")] public virtual async Task FileUploadAsync(Func> filePathFunc) { 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) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { var fileName = contentDisposition.FileName.Value; //处理压缩文件 if (fileName.Contains(".Zip", StringComparison.OrdinalIgnoreCase) || fileName.Contains(".rar", StringComparison.OrdinalIgnoreCase)) { var archive = ArchiveFactory.Open(section.Body); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { var serverFilePath = await filePathFunc(entry.Key); entry.WriteToFile(serverFilePath); } } } //普通单个文件 else { var serverFilePath = await filePathFunc(fileName); await FileStoreHelper.WriteFileAsync(section.Body, serverFilePath); } } section = await reader.ReadNextSectionAsync(); } } /// 流式上传 Dicom上传 [Route("base")] public virtual async Task DicomFileUploadAsync(Func filePathFunc, string boundary) { var fileCount = 0; var reader = new MultipartReader(boundary, HttpContext.Request.Body); var section = await reader.ReadNextSectionAsync(); while (section != null) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { var fileName = contentDisposition.FileName.Value ?? String.Empty; string mediaType = section.ContentType ?? String.Empty; //处理压缩文件 if (fileName.Contains(".Zip", StringComparison.OrdinalIgnoreCase) || fileName.Contains(".rar", StringComparison.OrdinalIgnoreCase)) { var archive = ArchiveFactory.Open(section.Body); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) { ++fileCount; await filePathFunc(entry.Key, entry.OpenEntryStream(), fileCount); } } } //普通单个文件 else { if (mediaType.Contains("octet-stream") || mediaType.Contains("dicom")) { ++fileCount; await filePathFunc(fileName, section.Body, fileCount); } } } section = await reader.ReadNextSectionAsync(); } } } #endregion #region Dicom 影像上传 临床数据 非diocm [ApiExplorerSettings(GroupName = "Image")] [ApiController] public class StudyController : UploadBaseController { public IMapper _mapper { get; set; } public IUserInfo _userInfo { get; set; } private readonly IMediator _mediator; private readonly IWebHostEnvironment _hostEnvironment; private readonly IRepository _repository; private readonly IEasyCachingProvider _provider; public StudyController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IMediator mediator, IEasyCachingProvider provider, IRepository repository) { _provider = provider; _hostEnvironment = hostEnvironment; _mediator = mediator; _mapper = mapper; _userInfo = userInfo; _repository = repository; } [HttpPost, Route("Study/PreArchiveStudy")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task PreArchiveStudy(PreArchiveStudyCommand preArchiveStudyCommand, [FromServices] IStudyService _studyService, [FromServices] IRepository _studyMonitorRepository) { if (_provider.Get>(StaticData.Anonymize.Anonymize_AddFixedFiled).Value == null) { await _mediator.Send(new AnonymizeCacheRequest()); } var savedInfo = _studyService.GetSaveToDicomInfo(preArchiveStudyCommand.SubjectVisitId); var studyMonitor = new StudyMonitor() { TrialId = savedInfo.TrialId, SiteId = savedInfo.SiteId, SubjectId = savedInfo.SubjectId, SubjectVisitId = savedInfo.SubjectVisitId, IsSuccess = false, UploadStartTime = DateTime.Now, IsDicom = preArchiveStudyCommand.IsDicom, IP = _userInfo.IP }; var addEntity = await _studyMonitorRepository.AddAsync(studyMonitor, true); return ResponseOutput.Ok(addEntity.Id); } /// Dicom 归档 [HttpPost, Route("Study/ArchiveStudy")] [DisableFormValueModelBinding] [DisableRequestSizeLimit] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task ArchiveStudyNew(/*[FromForm] ArchiveStudyCommand archiveStudyCommand,*/ Guid trialId, Guid subjectVisitId, string studyInstanceUid, Guid? abandonStudyId, Guid studyMonitorId, [FromServices] ILogger _logger, [FromServices] IEasyCachingProvider _provider, [FromServices] IStudyService _studyService, [FromServices] IHubContext _uploadHub, [FromServices] IDicomArchiveService _dicomArchiveService, [FromServices] IRepository _studyMonitorRepository ) { if (!HttpContext.Request.HasFormContentType || !MediaTypeHeaderValue.TryParse(HttpContext.Request.ContentType, out var mediaTypeHeader) || string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value)) { return ResponseOutput.NotOk("不支持的MediaType"); } var archiveStudyCommand = new ArchiveStudyCommand() { AbandonStudyId = abandonStudyId, StudyInstanceUid = studyInstanceUid, SubjectVisitId = subjectVisitId }; string studycode = string.Empty; var startTime = DateTime.Now; if (_provider.Exists($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}")) { return ResponseOutput.NotOk("当前已有人正在上传和归档该检查!"); } else { _provider.Set($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}", _userInfo.Id, TimeSpan.FromMinutes(30)); } //到了接口,代表上传结束了 var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == studyMonitorId); studyMonitor.UploadFinishedTime = DateTime.Now; var (archiveResult, archivedStudyIds) = (new DicomArchiveResult(), new List()); var (seriesInstanceUidList, sopInstanceUidList) = (new List(), new List()); //重传的时候,找出当前检查已经上传的series instance if (archiveStudyCommand.AbandonStudyId != null) { (seriesInstanceUidList, sopInstanceUidList) = _studyService.GetHasUploadSeriesAndInstance(archiveStudyCommand.AbandonStudyId.Value); } var savedInfo = _studyService.GetSaveToDicomInfo(archiveStudyCommand.SubjectVisitId); try { await DicomFileUploadAsync(async (fileName, fileStream, receivedCount) => { try { using (var memoryStream = new MemoryStream()) { await fileStream.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); var (studyId, studyCode) = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo, seriesInstanceUidList, sopInstanceUidList); if (!archivedStudyIds.Contains(studyId)) { archivedStudyIds.Add(studyId); archiveResult.ArchivedDicomStudies.Add(new DicomStudyBasicDTO() { StudyCode = studyCode, Id = studyId }); } } //await _uploadHub.Clients.All.ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount); await _uploadHub.Clients.User(_userInfo.Id.ToString()).ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount); archiveResult.ReceivedFileCount = receivedCount; } catch (Exception e) { _logger.LogError(e.Message + e.StackTrace); archiveResult.ErrorFiles.Add(fileName); } }, mediaTypeHeader.Boundary.Value); } catch (Exception ex) { _provider.Remove($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}"); throw new BusinessValidationFailedException("请求异常,请重试!"); } studyMonitor.FileSize = (decimal)HttpContext.Request.ContentLength; studyMonitor.FileCount = archiveResult.ReceivedFileCount; studyMonitor.FailedFileCount = archiveResult.ErrorFiles.Count; studyMonitor.IsDicomReUpload = archiveStudyCommand.AbandonStudyId != null; studyMonitor.Note = JsonConvert.SerializeObject(archiveResult); try { if (archivedStudyIds.Count > 0) // 上传成功,处理逻辑 { // 同一个检查批次 多个线程上传处理 批量保存 可能造成死锁 https://www.cnblogs.com/johnblogs/p/9945767.html await _dicomArchiveService.DicomDBDataSaveChange(); archiveResult.ReuploadNewStudyId = archivedStudyIds[0] == archiveStudyCommand.AbandonStudyId ? archivedStudyIds[0] : Guid.Empty; studyMonitor.IsSuccess = true; } } catch (Exception e) { studyMonitor.Note = JsonConvert.SerializeObject(new { Message = e.Message, Result = archiveResult }); _logger.LogError(e.Message + e.StackTrace); } finally { _provider.Remove($"StudyUid_{trialId}_{archiveStudyCommand.StudyInstanceUid}"); studyMonitor.StudyId = archiveResult.ArchivedDicomStudies.FirstOrDefault()?.Id ?? Guid.Empty; studyMonitor.StudyCode = archiveResult.ArchivedDicomStudies.FirstOrDefault()?.StudyCode; studyMonitor.ArchiveFinishedTime = DateTime.Now; await _studyMonitorRepository.SaveChangesAsync(); } return ResponseOutput.Result(studyMonitor.IsSuccess, archiveResult); } [HttpPost, Route("Study/CreateImageZip")] public async Task CreateImageZipAsync(Guid visitTaskId, [FromServices] IHubContext _uploadHub) { await _repository.BatchUpdateAsync(t => t.Id == visitTaskId, u => new VisitTask() { PackState = PackState.Packing }); var info = _repository.Where(t => t.Id == visitTaskId).Select(t => new { t.TrialId, t.Subject.SiteId, t.SubjectId, t.SourceSubjectVisitId }).FirstOrDefault(); var folderPath = FileStoreHelper.GetSubjectVisitDicomFolderPhysicalPath(_hostEnvironment, info.TrialId, info.SiteId, info.SubjectId, info.SourceSubjectVisitId.Value); // 获取文件夹中的文件列表 var files = Directory.GetFiles(folderPath); // 创建一个唯一的ZIP文件名 var zipFileName = $"archive_{DateTime.Now:yyyyMMddHHmmss}.zip"; var tempStoreFolder = Path.Combine(FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment), StaticData.Folder.TempFileFolder); if (!Directory.Exists(tempStoreFolder)) { Directory.CreateDirectory(tempStoreFolder); } var zipFilePath = Path.Combine(tempStoreFolder, zipFileName); var relativePath = $"/{StaticData.Folder.IRaCISDataFolder}/{StaticData.Folder.TempFileFolder}/{zipFileName}"; await Task.Run(async () => { ZipFile.CreateFromDirectory(folderPath, zipFilePath); await _repository.BatchUpdateAsync(t => t.Id == visitTaskId, u => new VisitTask() { PackState = PackState.Packed, PackRelativePath = relativePath }); }); // 发送最终进度信息到客户端 //await _uploadHub.Clients.User(_userInfo.Id.ToString()).CompressProgressAsync(visitTaskId, $"打包完成"); return ResponseOutput.Ok(relativePath); } [AllowAnonymous] [HttpGet("Study/DownloadImageZip")] public IActionResult DownloadImageZip(string relativePath) { var fileStorePath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, relativePath); new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(fileStorePath), out var contentType); return File(System.IO.File.OpenRead(fileStorePath), contentType ?? "application/octet-stream", Path.GetFileName(fileStorePath)); } /// /// 上传临床数据 多文件 /// /// /// [HttpPost("ClinicalData/UploadVisitClinicalData/{trialId:guid}/{subjectVisitId:guid}")] [DisableRequestSizeLimit] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task UploadVisitClinicalData(Guid subjectVisitId) { await QCCommon.VerifyIsCRCSubmmitAsync(_repository, _userInfo, subjectVisitId); var sv = _repository.Where(t => t.Id == subjectVisitId).Select(t => new { t.TrialId, t.SiteId, t.SubjectId }).FirstOrDefault().IfNullThrowException(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetClinicalDataPath(_hostEnvironment, fileName, sv.TrialId, sv.SiteId, sv.SubjectId, subjectVisitId); //插入临床pdf 路径 await _repository.AddAsync(new PreviousPDF() { SubjectVisitId = subjectVisitId, IsVisist = true, DataType = ClinicalDataType.MedicalHistory, UploadType = ClinicalUploadType.PDF, SubjectId = sv.SubjectId, TrialId = sv.TrialId, ClinicalLevel = ClinicalLevel.Subject, Path = relativePath, FileName = fileRealName }); return serverFilePath; }); await _repository.SaveChangesAsync(); return ResponseOutput.Ok(); } /// /// 上传临床数据模板 /// /// /// [HttpPost("ClinicalData/UploadClinicalTemplate")] [DisableRequestSizeLimit] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })] public async Task>> UploadClinicalTemplate(Guid? trialId) { if (trialId == null) trialId = default(Guid); var filerelativePath = string.Empty; List fileDtos = new List(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetClinicalTemplatePath(_hostEnvironment, fileName, trialId.Value); //插入临床pdf 路径 filerelativePath = relativePath; fileDtos.Add(new FileDto() { FileName = fileName, Path = relativePath }); await Task.CompletedTask; return serverFilePath; }); return ResponseOutput.Ok(fileDtos); } /// /// 上传阅片临床数据 /// /// /// /// /// [HttpPost("ClinicalData/UploadClinicalData/{trialId:guid}/{subjectId:guid}/{readingId:guid}")] [DisableRequestSizeLimit] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task>> UploadReadClinicalData(Guid trialId, Guid subjectId, Guid readingId) { var filerelativePath = string.Empty; List fileDtos = new List(); var siteid = await _repository.Where(x => x.Id == subjectId).Select(x => x.SiteId).FirstOrDefaultAsync(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetReadClinicalDataPath(_hostEnvironment, fileName, trialId, siteid, subjectId, readingId); //插入临床pdf 路径 filerelativePath = relativePath; fileDtos.Add(new FileDto() { FileName = fileName, Path = relativePath }); await Task.CompletedTask; return serverFilePath; }); return ResponseOutput.Ok(fileDtos); } /// /// 上传截图 /// /// /// [HttpPost("Printscreen/UploadPrintscreen/{subjectId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task> UploadPrintscreen(Guid subjectId) { var subjectInfo = await this._repository.Where(x => x.Id == subjectId).FirstNotNullAsync(); FileDto fileDto = new FileDto(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetUploadPrintscreenFilePath(_hostEnvironment, fileName, subjectInfo.TrialId, subjectInfo.SiteId, subjectInfo.Id); fileDto.Path = relativePath; fileDto.FileName = fileName; await Task.CompletedTask; return serverFilePath; }); return ResponseOutput.Ok(fileDto); } /// /// 上传Reading问题的图像 /// /// /// /// [HttpPost("VisitTask/UploadReadingAnswerImage/{trialId:guid}/{visitTaskId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task> UploadReadingAnswerImage(Guid trialId, Guid visitTaskId) { FileDto fileDto = new FileDto(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetFilePath(_hostEnvironment, fileName, trialId, visitTaskId, StaticData.Folder.JudgeTask); fileDto.Path = relativePath; fileDto.FileName = fileName; await Task.CompletedTask; return serverFilePath; }); return ResponseOutput.Ok(fileDto); } /// /// 上传裁判任务的图像 /// /// /// /// [HttpPost("VisitTask/UploadJudgeTaskImage/{trialId:guid}/{visitTaskId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task> UploadJudgeTaskImage(Guid trialId, Guid visitTaskId) { FileDto fileDto = new FileDto(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetFilePath(_hostEnvironment, fileName, trialId, visitTaskId, StaticData.Folder.JudgeTask); fileDto.Path = relativePath; fileDto.FileName = fileName; await Task.CompletedTask; return serverFilePath; }); return ResponseOutput.Ok(fileDto); } /// /// 上传医学审核图片 /// /// /// /// [HttpPost("TaskMedicalReview/UploadMedicalReviewImage/{trialId:guid}/{taskMedicalReviewId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task> UploadMedicalReviewImage(Guid trialId, Guid taskMedicalReviewId) { string path = string.Empty; FileDto fileDto = new FileDto(); await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetMedicalReviewImage(_hostEnvironment, fileName, trialId, taskMedicalReviewId); //await _repository.UpdatePartialFromQueryAsync(x => x.Id == taskMedicalReviewId, x => new TaskMedicalReview() //{ // ImagePath = relativePath, // FileName = fileName, //}); path = relativePath; fileDto.Path = relativePath; fileDto.FileName = fileName; return serverFilePath; }); await _repository.SaveChangesAsync(); return ResponseOutput.Ok(fileDto); } /// /// 上传非Dicom 文件 支持压缩包 多文件上传 /// /// /// /// /// /// /// //[DisableRequestSizeLimit] [RequestSizeLimit(1_073_741_824)] [HttpPost("NoneDicomStudy/UploadNoneDicomFile/{trialId:guid}/{subjectVisitId:guid}/{noneDicomStudyId:guid}/{studyMonitorId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task UploadNoneDicomFile(IFormCollection formCollection, Guid subjectVisitId, Guid noneDicomStudyId, Guid studyMonitorId, [FromServices] IRepository _noneDicomStudyRepository, [FromServices] IRepository _studyMonitorRepository) { await QCCommon.VerifyIsCRCSubmmitAsync(_repository, _userInfo, subjectVisitId); var sv = (await _repository.Where(t => t.Id == subjectVisitId).Select(t => new { t.TrialId, t.SiteId, t.SubjectId }).FirstOrDefaultAsync()).IfNullThrowConvertException(); var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == studyMonitorId); studyMonitor.UploadFinishedTime = DateTime.Now; await FileUploadAsync(async (fileName) => { var (serverFilePath, relativePath, fileRealName) = FileStoreHelper.GetNoneDicomFilePath(_hostEnvironment, fileName, sv.TrialId, sv.SiteId, sv.SubjectId, subjectVisitId); await _repository.AddAsync(new NoneDicomStudyFile() { FileName = fileRealName, Path = relativePath, NoneDicomStudyId = noneDicomStudyId }); return serverFilePath; }); var uploadFinishedTime = DateTime.Now; //// 上传非Dicom 后 将状态改为待提交 分为普通上传 和QC后重传 普通上传时才改为待提交 //await _repository.UpdatePartialFromQueryAsync(t => t.Id == subjectVisitId && t.SubmitState == SubmitStateEnum.None, u => new SubjectVisit() { SubmitState = SubmitStateEnum.ToSubmit }); var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync((t => t.Id == noneDicomStudyId)); noneDicomStudy.FileCount = noneDicomStudy.FileCount + formCollection.Files.Count; studyMonitor.FileCount = formCollection.Files.Count; studyMonitor.FileSize = formCollection.Files.Sum(t => t.Length); studyMonitor.IsDicom = false; studyMonitor.IsDicomReUpload = false; studyMonitor.StudyId = noneDicomStudyId; studyMonitor.StudyCode = noneDicomStudy.StudyCode; studyMonitor.ArchiveFinishedTime = DateTime.Now; studyMonitor.IP = _userInfo.IP; await _repository.SaveChangesAsync(); return ResponseOutput.Ok(); } /// /// 一致性核查 excel上传 支持三种格式 /// /// /// [HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] //[Authorize(Policy = IRaCISPolicy.PM_APM)] public async Task UploadVisitCheckExcel(Guid trialId) { var (serverFilePath, relativePath, fileName) = (string.Empty, string.Empty, string.Empty); await FileUploadAsync(async (realFileName) => { fileName = realFileName; if (!fileName.EndsWith(".xlsx") && !fileName.EndsWith(".csv") && !fileName.EndsWith(".xls")) { throw new BusinessValidationFailedException("支持.xlsx、.xls、.csv格式的文件上传。"); } (serverFilePath, relativePath) = FileStoreHelper.GetTrialCheckFilePath(_hostEnvironment, fileName, trialId); await _repository.AddAsync(new ConsistencyCheckFile() { TrialId = trialId, CreateTime = DateTime.Now, FileName = fileName, FilePath = relativePath, RelativePath = relativePath, CreateUserId = _userInfo.Id }); return serverFilePath; }); var etcCheckList = new List(); #region MiniExcel 需要自己验证数据格式规范 //if (fileName.EndsWith(".csv")) //{ // //因为csv 需要加配置文件 不然都是null // etcCheckList = MiniExcel.Query(filePath, null, configuration: config).ToList(); //} //else if (fileName.EndsWith(".xlsx")) //{ // // etcCheckList = MiniExcel.Query(filePath).ToList(); //} #endregion //Magicodes 支持自定义特性验证 if (fileName.EndsWith(".xlsx")) { var Importer = new ExcelImporter(); var import = await Importer.Import(System.IO.File.OpenRead(serverFilePath)); if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString()); //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors)); if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors)); etcCheckList = import.Data.ToList(); } else if (fileName.EndsWith(".csv")) { #region 临时方案 MiniExcel读取 然后保存为xlsx 再用 Magicodes验证数据 //因为csv 需要加配置文件 不然都是null etcCheckList = MiniExcel.Query(serverFilePath, null, configuration: new MiniExcelLibs.Csv.CsvConfiguration() { StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312")) }).ToList(); var (csVToXlsxPath, csVToXlsxRelativePath) = FileStoreHelper.GetTrialCheckFilePath(_hostEnvironment, Path.GetFileNameWithoutExtension(fileName) + ".xlsx", trialId); await MiniExcel.SaveAsAsync(csVToXlsxPath, etcCheckList, excelType: ExcelType.XLSX); var Importer = new ExcelImporter(); var import = await Importer.Import(System.IO.File.OpenRead(csVToXlsxPath)); if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString()); //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors)); if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors)); etcCheckList = import.Data.ToList(); #endregion #region 导入组件有问题 excel编码格式 //var Importer = new CsvImporter(); //var import = await Importer.Import(File.OpenRead(filePath)); //if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString()); //if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors)); //if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors)); //etcCheckList = import.Data.ToList(); #endregion } //ExcelReaderFactory 需要自己验证数据 并且从固定列取数据 else { //为了支持 xls 引入新的组件库 using (var stream = System.IO.File.Open(serverFilePath, FileMode.Open, FileAccess.Read)) { // Auto-detect format, supports: // - Binary Excel files (2.0-2003 format; *.xls) // - OpenXml Excel files (2007 format; *.xlsx, *.xlsb) using (var reader = ExcelReaderFactory.CreateReader(stream)) { // 2. Use the AsDataSet extension method var dateset = reader.AsDataSet(); foreach (DataRow col in dateset.Tables[0].Rows) { etcCheckList.Add(new CheckViewModel() { SiteCode = col[0].ToString(), SubjectCode = col[1].ToString(), VisitName = col[2].ToString(), StudyDate = col[3].ToString(), Modality = col[4].ToString(), }); } etcCheckList.Remove(etcCheckList[0]); // The result of each spreadsheet is in result.Tables } } } if (etcCheckList == null || etcCheckList.Count == 0) { return ResponseOutput.NotOk("请保证上传数据符合模板文件中的样式,且存在有效数据。"); } else { //处理Excel 有时只是清除某些行的数据 读取也会读到数据,只是数据是null 后面处理的时候转为字符串为报错 etcCheckList.ForEach(t => { t.Modality = t.Modality ?? string.Empty; t.SiteCode = t.SiteCode ?? string.Empty; t.SubjectCode = t.SubjectCode ?? string.Empty; t.VisitName = t.VisitName ?? string.Empty; t.StudyDate = t.StudyDate ?? string.Empty; }); var dt = DateTime.Now; etcCheckList = etcCheckList.Where(t => !(t.Modality == string.Empty || t.SiteCode == string.Empty || t.SubjectCode == string.Empty || t.VisitName == string.Empty || t.StudyDate == string.Empty || !DateTime.TryParse(t.StudyDate, out dt))).ToList(); if (etcCheckList.Count == 0) { return ResponseOutput.NotOk("请保证上传数据符合模板文件中的样式,且存在有效数据。"); } } await _mediator.Send(new ConsistencyVerificationRequest() { ETCList = etcCheckList, TrialId = trialId }); return ResponseOutput.Ok(); } } #endregion #region 医生文件上传下载 /// 医生文件上传下载 [ApiExplorerSettings(GroupName = "Common")] [ApiController] public class FileController : UploadBaseController { public IMapper _mapper { get; set; } public IUserInfo _userInfo { get; set; } private readonly IWebHostEnvironment _hostEnvironment; private readonly IFileService _fileService; public FileController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IFileService fileService) { _fileService = fileService; _hostEnvironment = hostEnvironment; _mapper = mapper; _userInfo = userInfo; } /// /// 上传文件[FileUpload] /// /// 附件类型 /// 医生Id /// 返回文件信息 [HttpPost, Route("file/UploadFile/{attachmentType}/{doctorId}")] [DisableFormValueModelBinding] [DisableRequestSizeLimit] public async Task UploadOrdinaryFile(string attachmentType, Guid doctorId) { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetDoctorOrdinaryFilePath(_hostEnvironment, fileName, doctorId, attachmentType)); } /// /// 上传文件( 不是医生个人的文件)[FileUpload] /// 例如:阅片章程等 /// /// 文件类型 /// [HttpPost, Route("file/UploadNonDoctorFile/{type}")] [DisableFormValueModelBinding] [DisableRequestSizeLimit] public async Task UploadNonDoctorFile(string type) { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetNonDoctorFilePath(_hostEnvironment, fileName, type)); } /// /// 下载多个医生的所有附件 /// /// /// [HttpPost, Route("file/downloadDoctorAttachments")] public async Task> DownloadAttachment(Guid[] doctorIds) { var path = await _fileService.CreateDoctorsAllAttachmentZip(doctorIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = path, FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } /// /// 下载医生官方简历 /// /// /// /// [HttpPost, Route("file/downloadOfficialCV/{language}")] public async Task> DownloadOfficialResume(int language, Guid[] doctorIds) { var path = await _fileService.CreateDoctorsAllAttachmentZip(doctorIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = await _fileService.CreateOfficialResumeZip(language, doctorIds), FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } /// /// 下载指定医生的指定附件 /// /// 医生Id /// 要下载的附件Id /// [HttpPost, Route("file/downloadByAttachmentId/{doctorId}")] public async Task> DownloadAttachmentById(Guid doctorId, Guid[] attachmentIds) { var path = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds), FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } [HttpPost, Route("enroll/downloadResume/{trialId:guid}/{language}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [AllowAnonymous] public async Task> DownloadResume(int language, Guid trialId, Guid[] doctorIdArray) { var zipPath = await _fileService.CreateOfficialResumeZip(language, doctorIdArray); return ResponseOutput.Ok(zipPath); } } #endregion #region 项目 系统 基本文件 上传 下载 预览 [ApiExplorerSettings(GroupName = "Common")] [ApiController] public class UploadDownLoadController : UploadBaseController { public IMapper _mapper { get; set; } public IUserInfo _userInfo { get; set; } private readonly IMediator _mediator; private readonly IWebHostEnvironment _hostEnvironment; public UploadDownLoadController(IMapper mapper, IUserInfo userInfo, IMediator mediator, IWebHostEnvironment hostEnvironment) { _hostEnvironment = hostEnvironment; _mediator = mediator; _mapper = mapper; _userInfo = userInfo; } /// 缩略图 [AllowAnonymous] [HttpGet("Common/LocalFilePreview")] public async Task LocalFilePreview(string relativePath) { var _fileStorePath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, relativePath); var storePreviewPath = _fileStorePath + ".preview.jpeg"; if (!System.IO.File.Exists(storePreviewPath)) { ImageHelper.ResizeSave(_fileStorePath, storePreviewPath); } return new FileContentResult(await System.IO.File.ReadAllBytesAsync(storePreviewPath), "image/jpeg"); } /// 通用文件下载 [AllowAnonymous] [HttpGet("CommonDocument/DownloadCommonDoc")] public async Task DownloadCommonFile(string code, [FromServices] IRepository _commonDocumentRepository) { var (filePath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code); new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType); return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName); } /// /// 下载项目临床数据文件 /// /// /// /// [AllowAnonymous] [HttpGet("CommonDocument/DownloadTrialClinicalFile")] public async Task DownloadTrialClinicalFile(Guid clinicalDataTrialSetId, [FromServices] IRepository _clinicalDataTrialSetRepository) { var (filePath, fileName) = await FileStoreHelper.GetTrialClinicalPathAsync(_hostEnvironment, _clinicalDataTrialSetRepository, clinicalDataTrialSetId); new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType); return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName); } /// /// 下载系统临床数据文件 /// /// /// /// [AllowAnonymous] [HttpGet("CommonDocument/DownloadSystemClinicalFile")] public async Task DownloadSystemClinicalFile(Guid clinicalDataSystemSetId, [FromServices] IRepository _clinicalDataSystemSetRepository) { var (filePath, fileName) = await FileStoreHelper.GetSystemClinicalPathAsync(_hostEnvironment, _clinicalDataSystemSetRepository, clinicalDataSystemSetId); new FileExtensionContentTypeProvider().Mappings.TryGetValue(Path.GetExtension(filePath), out var contentType); return File(System.IO.File.OpenRead(filePath), contentType ?? "application/octet-stream", fileName); } /// ///上传项目签名文档 /// /// /// [HttpPost("TrialDocument/UploadTrialDoc/{trialId:guid}")] [DisableRequestSizeLimit] [DisableFormValueModelBinding] public async Task UploadTrialDoc(Guid trialId) { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetTrialSignDocPath(_hostEnvironment, trialId, fileName)); } /// /// 上传系统签名文档 /// /// [HttpPost("TrialDocument/UploadSystemDoc")] [DisableRequestSizeLimit] [DisableFormValueModelBinding] public async Task UploadSysTemDoc() { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemSignDocPath(_hostEnvironment, fileName)); } /// /// 上传通用文档 比如一致性核查的 比如导出的excel 模板 /// /// [HttpPost("CommonDocument/UploadCommonDoc")] [DisableRequestSizeLimit] [DisableFormValueModelBinding] public async Task UploadCommonDoc() { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetCommonDocPath(_hostEnvironment, fileName)); } /// /// 上传系统通知文档 /// /// [HttpPost("SystemNotice/UploadSystemNoticeDoc")] [DisableRequestSizeLimit] [DisableFormValueModelBinding] public async Task UploadSystemNoticeDoc() { return await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemNoticePath(_hostEnvironment, fileName)); } } #endregion }