diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 9f89c14c3..fdccfab3e 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -30,7 +30,10 @@ namespace IRaCIS.Core.Application.Service public class TrialImageDownloadService(IRepository _trialRepository, IRepository _subjectVisitRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment, IRepository _studyRepository, IRepository _seriesRepository, - IRepository _instanceRepository) : BaseService + IRepository _instanceRepository, + IRepository _taskInstanceRepository, + IRepository _visitTaskRepository + ) : BaseService { @@ -491,6 +494,325 @@ namespace IRaCIS.Core.Application.Service return ResponseOutput.Ok(); } + + + [HttpPost] + [AllowAnonymous] + public async Task DownloadExcelTrialTaskImageZIPStream(Guid trialId) + { + var trialInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo }).FirstOrDefault(); + + #region 设置目录 + + var rootFolder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment); + Directory.CreateDirectory(rootFolder); + + // 获取无效字符(系统定义的) + string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); + + // 用正则表达式替换所有非法字符为下划线或空字符 + string pattern = $"[{Regex.Escape(invalidChars)}]"; + + var regexNo = Regex.Replace(trialInfo.ResearchProgramNo, pattern, "_"); + + // 创建一个临时文件夹来存放文件 + string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}");//_{NewId.NextGuid()} + Directory.CreateDirectory(trialFolderPath); + + #endregion + + var oldVisits = MiniExcel.Query(Path.Combine(rootFolder, "Old.xlsx")).ToList(); + + var downloadTasks = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.TaskName.IsNotNullOrEmpty() && t.TaskBlindName.IsNotNullOrEmpty()).ToList(); + + downloadTasks = downloadTasks.Where(t => !oldVisits.Any(old => old.SubjectCode == t.SubjectCode && old.TaskBlindName == t.TaskBlindName && old.TaskName.Trim().ToLower() == t.TaskName.Trim().ToLower())).ToList(); + + var visitIndex = 0; + var skipCount = 0; + foreach (var downloadTask in downloadTasks) + { + visitIndex++; + var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new + { + t.ResearchProgramNo, + t.TrialCode, + + TaskList = t.VisitTaskList.Where(t => t.TaskState == TaskState.Effect && t.DoctorUser.UserName == downloadTask.UserName + && t.IsAnalysisCreate == false) + .Where(t => t.TaskName.Trim() == downloadTask.TaskName.Trim() + && t.Subject.Code.Trim() == downloadTask.SubjectCode.Trim() + && t.TaskBlindName.Trim() == downloadTask.TaskBlindName.Trim() + ) + .Select(vt => new + { + VisitTaskId = vt.Id, + TrialSiteCode = vt.SourceSubjectVisit.TrialSite.TrialSiteCode, + SubjectCode = vt.Subject.Code, + UserName = vt.DoctorUser.UserName, + vt.TaskName, + vt.TaskBlindName, + VisitName = vt.SourceSubjectVisit.VisitName, + VisitNum = vt.SourceSubjectVisit.VisitNum, + StudyList = vt.TaskStudyList.Select(u => new + { + StudyId = u.Id, + u.PatientId, + u.StudyTime, + u.StudyCode, + u.StudyInstanceUid, + u.StudyDIRPath, + + SeriesList = u.SeriesList.Select(z => new + { + z.Modality, + + InstancePathList = z.InstanceList.Select(k => new + { + InstanceId = k.Id, + k.Path, + k.IsEncapsulated, + k.NumberOfFrames, + }).ToList() + }) + + }).ToList(), + + NoneDicomStudyList = vt.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.IsReading && t.Modality != "OCT").Select(nd => new + { + nd.Modality, + nd.StudyCode, + nd.ImageDate, + + FileList = nd.TaskNoneDicomFileList.Where(t => t.IsReading && t.VisitTaskId == vt.Id).Select(file => new + { + file.FileName, + file.Path, + file.FileType + }).ToList() + }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() + + }).FirstOrDefault(); + + if (downloadInfo == null) + { + continue; + } + + #region 排除已经下载的 + + var logFilePath = Path.Combine(rootFolder, $"{trialId}_{regexNo}_download_log.csv"); + + if (File.Exists(logFilePath)) + { + var existVisits = MiniExcel.Query(logFilePath, configuration: new MiniExcelLibs.Csv.CsvConfiguration() + { + StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312")) + }).ToList(); + + if (existVisits.Any(old => old.TaskName == downloadTask.TaskName + && old.SubjectCode == downloadTask.SubjectCode + && old.TaskBlindName.Trim().ToLower() == downloadTask.TaskBlindName.Trim().ToLower() + && old.UserName.Trim().ToLower() == downloadTask.UserName.Trim().ToLower() + )) + { + Log.Logger.Warning($"[{visitIndex}] Excel显示已下载,跳过当前:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}"); + skipCount++; + continue; + } + + } + + + #endregion + + foreach (var taskItem in downloadInfo.TaskList) + { + if (taskItem.StudyList.Count() == 0 && taskItem.NoneDicomStudyList.SelectMany(t=>t.FileList).Count() == 0) + { + Log.Logger.Warning($"[{visitIndex}]查询无检查,跳过当前访视:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}"); + skipCount++; + continue; + } + + try + { + #region 导出访视 + + var zipItems = new List(); + + var visitFolderName = $"{taskItem.SubjectCode}_{taskItem.TaskBlindName.Trim()}"; + + #region DICOM + + foreach (var studyInfo in taskItem.StudyList) + { + var dirDic = new Dictionary(); + + var studyFolderName = $"{studyInfo.StudyCode}_{studyInfo.StudyTime:yyyy-MM-dd}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality).Distinct())}"; + + #region DICOMDIR + + + if (!_taskInstanceRepository.Where(t => taskItem.VisitTaskId == t.VisitTaskId).Any(c => c.TransferSytaxUID == string.Empty)) + { + var list = _visitTaskRepository.Where(t => t.Id == taskItem.VisitTaskId).SelectMany(t => t.TaskStudyList) + .SelectMany(t => t.InstanceList.Where(t => t.StudyId == studyInfo.StudyId)) + .Select(t => new StudyDIRInfo() + { + + DicomStudyId = t.TaskStudy.Id, + + PatientId = downloadInfo.TrialCode + "-" + t.TaskStudy.Subject.Code, + PatientName = t.TaskStudy.PatientName, + PatientBirthDate = t.TaskStudy.PatientBirthDate, + PatientSex = t.TaskStudy.PatientSex, + + StudyInstanceUid = t.StudyInstanceUid, + StudyId = t.TaskStudy.StudyId, + DicomStudyDate = t.TaskStudy.DicomStudyDate, + DicomStudyTime = t.TaskStudy.DicomStudyTime, + AccessionNumber = t.TaskStudy.AccessionNumber, + + StudyDescription = t.TaskStudy.Description, + + SeriesInstanceUid = t.TaskSeries.SeriesInstanceUid, + Modality = t.TaskSeries.Modality, + DicomSeriesDate = t.TaskSeries.DicomSeriesDate, + DicomSeriesTime = t.TaskSeries.DicomSeriesTime, + SeriesNumber = t.TaskSeries.SeriesNumber, + SeriesDescription = t.TaskSeries.Description, + + InstanceId = t.Id, + SopInstanceUid = t.SopInstanceUid, + SOPClassUID = t.SOPClassUID, + InstanceNumber = t.InstanceNumber, + MediaStorageSOPClassUID = t.MediaStorageSOPClassUID, + MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID, + TransferSytaxUID = t.TransferSytaxUID, + + }).ToList(); + + foreach (var group in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId })) + { + + using var ms = new MemoryStream(); + + await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, outputStream: ms); + + var dicomDirBytes = ms.ToArray(); + + zipItems.Add(new ZipItem + { + ZipEntryPath = $"{visitFolderName}/{studyFolderName}/DICOMDIR", + + CustomWriter = async entryStream => + { + await entryStream.WriteAsync(dicomDirBytes); + } + }); + } + + } + + + #endregion + + + + + foreach (var seriesInfo in studyInfo.SeriesList) + { + foreach (var instanceInfo in seriesInfo.InstancePathList) + { + zipItems.Add(new ZipItem + { + OssPath = instanceInfo.Path, + + IsEncapsulated = instanceInfo.IsEncapsulated, + + ZipEntryPath = $"{visitFolderName}/" + $"{studyFolderName}/" + $"IMAGE/" + $"{dirDic[instanceInfo.InstanceId.ToString()]}" + }); + } + } + + + + } + + #endregion + + + #region NoneDicom + + foreach (var study in taskItem.NoneDicomStudyList) + { + var studyFolderName = $"{study.StudyCode}_" + $"{study.ImageDate:yyyy-MM-dd}_" + $"{study.Modality}"; + + foreach (var file in study.FileList) + { + zipItems.Add(new ZipItem + { + OssPath = HttpUtility.UrlDecode(file.Path), + + ZipEntryPath = $"{visitFolderName}/" + $"{studyFolderName}/" + $"{Path.GetFileName(file.FileName)}" + }); + } + } + + #endregion + + + #region zip + + + var zipPath = Path.Combine(trialFolderPath, visitFolderName + ".zip"); + + Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始打包下载访视:{visitFolderName}"); + + try + { + await CreateVisitZipAsync(zipPath, zipItems); + + //Log.Logger.Warning($"zip exists={File.Exists(zipPath)} size={new FileInfo(zipPath).Length}"); + + Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 下载完成:{visitFolderName}"); + + DownloadLogger.TaskWrite(logFilePath, taskItem.SubjectCode, taskItem.TaskName, taskItem.TaskBlindName, taskItem.UserName, "Success"); + + } + catch (Exception ex) + { + Log.Logger.Warning($"出现异常{ex}自动删除压缩包:{visitFolderName}"); + //如果有异常,删除失败的压缩包 + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + } + + + #endregion + + + + #endregion + + } + + catch (Exception ex) + { + Log.Logger.Error(ex, $"导出失败:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}"); + } + + + } + } + + return ResponseOutput.Ok(); + } + #endregion @@ -542,6 +864,53 @@ namespace IRaCIS.Core.Application.Service writer.WriteLine(line); } + + public static void TaskWrite( + string logFilePath, + string subjectCode, + string taskName, + string taskBlindName, + string userName, + string? message = null) + { + bool fileExists = File.Exists(logFilePath); + + // 一次性打开文件流 + using var stream = new FileStream( + logFilePath, + FileMode.Append, + FileAccess.Write, + FileShare.ReadWrite); + + // 首次创建时写入 BOM + if (!fileExists) + { + var bom = new UTF8Encoding(true).GetPreamble(); + stream.Write(bom, 0, bom.Length); + } + + using var writer = new StreamWriter(stream, new UTF8Encoding(false)); + + // 写入表头 + if (!fileExists) + { + writer.WriteLine("Time,SubjectCode,TaskName,TaskBlindName,UserName,Message"); + } + + // 写入数据 + string line = string.Join(",", + [ + Escape(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), + subjectCode, + taskName, + taskBlindName, + userName, + Escape(message) + ]); + + writer.WriteLine(line); + } + private static string Escape(string? value) { if (string.IsNullOrEmpty(value)) diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 70f40183a..9aeb6934a 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -295,6 +295,21 @@ namespace IRaCIS.Core.Application.Service } + public class SubjectTaskExcel + { + + public string SubjectCode { get; set; } + + public string TaskName { get; set; } + + public string TaskBlindName { get; set; } + + public string UserName { get; set; } + + } + + + /// /// 访视数据模型 ///