From 40f744ea4a49e345120053257678c357a7cc58d0 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 29 May 2026 15:16:35 +0800 Subject: [PATCH 01/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=BD=B1=E5=83=8F=E6=89=93=E6=88=90=E5=8E=8B=E7=BC=A9=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 365 +++++++++++++----- 1 file changed, 258 insertions(+), 107 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index a23703c16..faf9d5274 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -8,6 +8,7 @@ using FellowOakDicom.Imaging; using FellowOakDicom.Imaging.Render; using FellowOakDicom.IO.Buffer; using IRaCIS.Core.Application.Helper; +using IRaCIS.Core.Application.MassTransit.Command; using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Infrastructure; @@ -18,17 +19,20 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; using NPOI.Util; +using Org.BouncyCastle.Utilities.Zlib; using SharpCompress.Common; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; +using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; +using static IRaCIS.Core.Application.Service.TestService; using static IRaCIS.Core.Domain.Share.StaticData; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; @@ -97,6 +101,68 @@ namespace IRaCIS.Core.Application.Service } + + public static class DownloadLogger + { + + + public static void Write( + string logFilePath, + string subjectCode, + decimal visitNum, + string visitName, + string? message = null) + { + + bool fileExists = File.Exists(logFilePath); + + using var stream = new FileStream( + logFilePath, + FileMode.Append, + FileAccess.Write, + FileShare.ReadWrite); + + using var writer = new StreamWriter(stream, Encoding.UTF8); + + + // 首次写入表头 + if (!fileExists) + { + writer.WriteLine("Time,SubjectCode,VisitNum,VisitName,Message"); + } + + string line = string.Join(",", + [ + Escape(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), + subjectCode, + visitNum, + visitName, + Escape(message) + ]); + + writer.WriteLine(line); + } + + // 防止逗号、换行导致 CSV 错乱 + private static string Escape(string? value) + { + if (string.IsNullOrEmpty(value)) + return ""; + + value = value.Replace("\"", "\"\""); + + return $"\"{value}\""; + } + } + + public class DownloadJob + { + public string Name { get; set; } + + + public Func Action { get; set; } + } + /// /// 后端api swagger 下载项目影像 /// @@ -114,13 +180,14 @@ namespace IRaCIS.Core.Application.Service { t.ResearchProgramNo, - VisitList = t.SubjectVisitList + VisitList = t.SubjectVisitList.Where(t => t.VisitTaskList.Any(t => t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId != null && t.DoctorUserId != null)) //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) .Select(sv => new { TrialSiteCode = sv.TrialSite.TrialSiteCode, SubjectCode = sv.Subject.Code, VisitName = sv.VisitName, + VisitNum = sv.VisitNum, StudyList = sv.StudyList.Select(u => new { u.PatientId, @@ -154,159 +221,243 @@ namespace IRaCIS.Core.Application.Service file.FileType }).ToList() }).ToList() - }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() }).FirstOrDefault(); + if (downloadInfo == null) + { + return ResponseOutput.Ok(); + } - var count = downloadInfo.VisitList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count(); - var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count(); + #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(downloadInfo.ResearchProgramNo, pattern, "_"); + + // 创建一个临时文件夹来存放文件 + string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}");//_{NewId.NextGuid()} + Directory.CreateDirectory(trialFolderPath); + + var oldVisits = MiniExcel.Query(Path.Combine(rootFolder, "Old.xlsx")).ToList(); + + var acturalDownList = downloadInfo.VisitList.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && + old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + + #endregion + + #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(); + + acturalDownList = acturalDownList.Where(t => !existVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && + old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + + + } + + #endregion + + var count = acturalDownList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count(); + + var count2 = acturalDownList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count(); Console.WriteLine($"下载总数量:{count}+{count2}={count + count2}"); - if (downloadInfo != null) + var downloadJobs = new List(); + + foreach (var visitItem in acturalDownList) { - var downloadJobs = new List>(); - - //var rootFolder = @"E:\DownloadImage"; - - var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment); - - // 获取无效字符(系统定义的) - string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); - - // 用正则表达式替换所有非法字符为下划线或空字符 - string pattern = $"[{Regex.Escape(invalidChars)}]"; - - var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_"); - - // 创建一个临时文件夹来存放文件 - string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}"); - Directory.CreateDirectory(trialFolderPath); - - foreach (var visitItem in downloadInfo.VisitList) + if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) { - if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) + continue; + } + + #region 处理 中心,受试者dicom non-dicom 文件夹层级 + + //var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode); + //if (!Directory.Exists(siteFolderPath)) + //{ + // Directory.CreateDirectory(siteFolderPath); + //} + + #endregion + + + foreach (var studyInfo in visitItem.StudyList) + { + // 遍历 Series + foreach (var seriesInfo in studyInfo.SeriesList) { - continue; - } + string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); - #region 处理 中心,受试者dicom non-dicom 文件夹层级 + // 创建 影像 文件夹 + Directory.CreateDirectory(studyDicomFolderPath); - var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode); - if (!Directory.Exists(siteFolderPath)) - { - Directory.CreateDirectory(siteFolderPath); - } - - #endregion - - - foreach (var studyInfo in visitItem.StudyList) - { - // 遍历 Series - foreach (var seriesInfo in studyInfo.SeriesList) + // 遍历 InstancePathList + foreach (var instanceInfo in seriesInfo.InstancePathList) { - string studyDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_DICOM", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); + // 复制文件到相应的文件夹 + string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path)); - // 创建 影像 文件夹 - Directory.CreateDirectory(studyDicomFolderPath); - // 遍历 InstancePathList - foreach (var instanceInfo in seriesInfo.InstancePathList) + downloadJobs.Add(new DownloadJob() { - // 复制文件到相应的文件夹 - string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path)); + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_DICOM_{destinationPath}", + Action = async () => + { await using var output = File.Create(destinationPath); + if (instanceInfo.IsEncapsulated) { - //加入到下载任务里 - downloadJobs.Add(() => TryWriteMergedDicomAsync(() => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path), output)); + await TryWriteMergedDicomAsync( + () => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path), + output); } else { - //加入到下载任务里 - downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath)); + await using var input = + await _oSSService.GetStreamFromOSSAsync(instanceInfo.Path); + + await input.CopyToAsync(output); } - - - //下载到当前目录 - //await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath); } - } + }); - - } - - foreach (var noneDicomStudy in visitItem.NoneDicomStudyList) - { - string studyNoneDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_Non-DICOM", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); - - Directory.CreateDirectory(studyNoneDicomFolderPath); - - foreach (var file in noneDicomStudy.FileList) - { - string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName)); - - //加入到下载任务里 - downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath)); - //下载到当前目录 - //await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath); } } } - #region 异步方式处理 - - int totalCount = downloadJobs.Count; - int downloadedCount = 0; - - foreach (var job in downloadJobs) + foreach (var noneDicomStudy in visitItem.NoneDicomStudyList) { - try + string studyNoneDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); + + Directory.CreateDirectory(studyNoneDicomFolderPath); + + foreach (var file in noneDicomStudy.FileList) { - await job(); - } - catch (Exception ex) - { - Log.Logger.Error($"下载失败: {ex.Message}"); - } + string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName)); - downloadedCount++; - - // 每处理50个,输出一次进度(或最后一个时也输出) - if (downloadedCount % 50 == 0 || downloadedCount == totalCount) - { - - Log.Logger.Error($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + //加入到下载任务里 + downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_NoneDICOM_{destinationPath}", Action = () => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath) }); + //下载到当前目录 + //await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath); } } - #endregion - #region 多线程测试 - //const int batchSize = 15; - //int totalCount = downloadJobs.Count; - //int downloadedCount = 0; + //建立压缩包 + string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}"); - //for (int i = 0; i < downloadJobs.Count; i += batchSize) - //{ - // var batch = downloadJobs.Skip(i).Take(batchSize).Select(job => job()); + downloadJobs.Add(new DownloadJob() + { + Name = $"{visitItem.SubjectCode}_{visitItem.VisitName}_Zip", - // await Task.WhenAll(batch); + Action = async () => + { + string zipPath = visitFolderPath + ".zip"; - // downloadedCount += batch.Count(); + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } - // Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); - //} - #endregion + ZipFile.CreateFromDirectory( + visitFolderPath, + zipPath, + CompressionLevel.Fastest, + false); + Directory.Delete(visitFolderPath, true); + + await Task.CompletedTask; + } + }); + + //记录日志 + downloadJobs.Add(new DownloadJob() + { + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_Finished", + Action = () => + { + DownloadLogger.Write( + logFilePath: logFilePath, + subjectCode: visitItem.SubjectCode, + visitNum: visitItem.VisitNum, + visitName: visitItem.VisitName, + message: "Success"); + + return Task.CompletedTask; + } + }); } + #region 异步方式处理 + + int totalCount = downloadJobs.Count; + int downloadedCount = 0; + + foreach (var job in downloadJobs) + { + try + { + await job.Action(); + } + catch (Exception ex) + { + Log.Logger.Error($"{job.Name}下载失败: {ex.Message}"); + } + + downloadedCount++; + + // 每处理50个,输出一次进度(或最后一个时也输出) + if (downloadedCount % 50 == 0 || downloadedCount == totalCount) + { + + Log.Logger.Warning($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + + } + } + #endregion + + #region 多线程测试 + + //const int batchSize = 15; + //int totalCount = downloadJobs.Count; + //int downloadedCount = 0; + + //for (int i = 0; i < downloadJobs.Count; i += batchSize) + //{ + // var batch = downloadJobs.Skip(i).Take(batchSize).Select(job => job()); + + // await Task.WhenAll(batch); + + // downloadedCount += batch.Count(); + + // Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + //} + #endregion + + return ResponseOutput.Ok(); From 76c69a8464245fd90d9eaa687cea9ffd708b036a Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 29 May 2026 17:51:27 +0800 Subject: [PATCH 02/25] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=8C=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IRaCIS.Core.Application.xml | 10 ++++++---- .../Common/TrialImageDownloadService.cs | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index e5cf503de..c0882ff60 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -1767,11 +1767,13 @@ - + 后端api swagger 下载项目影像 + + @@ -17494,17 +17496,17 @@ - ���� + 质疑 - һ���Ժ˲� + 一致性核查 - ���� + 复制 diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index faf9d5274..8cac5500d 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -167,13 +167,20 @@ namespace IRaCIS.Core.Application.Service /// 后端api swagger 下载项目影像 /// /// + /// + /// /// [HttpPost] [AllowAnonymous] - public async Task DownloadTrialImage(Guid trialId) + public async Task DownloadTrialImage(Guid trialId, int startIndex, int endIndex) { //找到项目里面未阅片的影像 + if (startIndex < 1 || endIndex < 1 || startIndex > endIndex) + { + return ResponseOutput.NotOk("请输入正确的下载区间"); + } + //var subjectCodeList = new List() { "05002", "07006", "07026" }; var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new @@ -249,6 +256,8 @@ namespace IRaCIS.Core.Application.Service var oldVisits = MiniExcel.Query(Path.Combine(rootFolder, "Old.xlsx")).ToList(); + Log.Logger.Warning($"数据库查询下载访视数量 - 前一批已下载访视数量:{downloadInfo.VisitList.Count} - {oldVisits.Count}"); + var acturalDownList = downloadInfo.VisitList.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); @@ -265,19 +274,25 @@ namespace IRaCIS.Core.Application.Service StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312")) }).ToList(); + Log.Logger.Warning($"download_log.csv访视数量:{existVisits.Count}"); + acturalDownList = acturalDownList.Where(t => !existVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); } + acturalDownList = acturalDownList.Skip(startIndex - 1).Take(endIndex).ToList(); + + Log.Logger.Warning($"该区{startIndex}-{endIndex} 实际需要下载访视数量:{acturalDownList.Count}"); + #endregion var count = acturalDownList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count(); var count2 = acturalDownList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count(); - Console.WriteLine($"下载总数量:{count}+{count2}={count + count2}"); + Log.Logger.Warning($"下载总数量:{count}+{count2}={count + count2}"); var downloadJobs = new List(); From 41efcbfdf11c0c2a4fb9359f0ff05e7598bfa529 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 1 Jun 2026 12:41:54 +0800 Subject: [PATCH 03/25] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E9=85=8D=E7=BD=AE=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?windows=E9=83=A8=E7=BD=B2=E8=AF=86=E5=88=AB=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 3 +- .../Properties/launchSettings.json | 4 +- .../appsettings.Prod_Event_IRC.json | 83 ++++++++++--------- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 0ff83fb8f..01df023b5 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -65,7 +65,8 @@ SerilogExtension.AddSerilogSetup(enviromentName); var builder = WebApplication.CreateBuilder(new WebApplicationOptions { - EnvironmentName = enviromentName + EnvironmentName = enviromentName, + Args = args }); #region 主机配置 diff --git a/IRaCIS.Core.API/Properties/launchSettings.json b/IRaCIS.Core.API/Properties/launchSettings.json index d3e1c91fe..3238a6f17 100644 --- a/IRaCIS.Core.API/Properties/launchSettings.json +++ b/IRaCIS.Core.API/Properties/launchSettings.json @@ -33,11 +33,11 @@ }, "applicationUrl": "http://0.0.0.0:6100" }, - "IRaCIS.Event_IRC": { + "IRaCIS.Prod_Event_IRC": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Event_IRC" + "ASPNETCORE_ENVIRONMENT": "Prod_Event_IRC" }, "applicationUrl": "http://0.0.0.0:6100" }, diff --git a/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json b/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json index cf8f44b59..9c15f6a86 100644 --- a/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json +++ b/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json @@ -7,8 +7,10 @@ } }, "ConnectionStrings": { - "RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true", - "Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true" + "RemoteNew": "Server=10.10.10.49,1433;Database=Prod_IRC_pidr_restore;User ID=sa;Password=xc@123456;TrustServerCertificate=true", + "Hangfire": "Server=10.10.10.49,1433;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true" + //"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true", + //"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true" }, "WeComNoticeConfig": { "IsOpenWeComNotice": true, @@ -17,48 +19,51 @@ "VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ] }, "ObjectStoreService": { - "ObjectStoreUse": "AliyunOSS", - + "IsOpenStoreSync": true, + "ApiDeployRegion": "CN", + "SyncConfigList": [ + { + "Domain": "irc.extimaging.com", + "Primary": "AliyunOSS", + "Target": "AWS", + "UploadRegion": "CN", + "TargetRegion": "US", + "IsOpenSync": true + }, + { + "Domain": "eiirc.extimaging.com", + "Primary": "AWS", + "Target": "AliyunOSS", + "UploadRegion": "US", + "TargetRegion": "CN", + "IsOpenSync": true + } + ], "AliyunOSS": { "RegionId": "cn-shanghai", "InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com", "EndPoint": "https://oss-cn-shanghai.aliyuncs.com", - "AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y", - "AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T", - "RoleArn": "acs:ram::1078130221702011:role/uat-oss-access", - "BucketName": "tl-med-irc-event-store", - "ViewEndpoint": "https://tl-med-irc-event-store.oss-cn-shanghai.aliyuncs.com", + "AccessKeyId": "LTAI5tNRTsqL6aWmHkDmTwoH", + "AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH", + "RoleArn": "acs:ram::1899121822495495:role/irc-oss-access", + "BucketName": "zy-irc-store", + "ViewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com", "Region": "oss-cn-shanghai", "DurationSeconds": 7200 }, - - "MinIO": { - "endPoint": "hir-oss.uat.extimaging.com", - "port": "80", - "useSSL": false, - "viewEndpoint": "http://hir-oss.uat.extimaging.com/irc-uat", - //"port": "443", - //"useSSL": true, - //"viewEndpoint": "https://hir-oss.uat.extimaging.com/irc-uat", - "accessKey": "b9Ul0e98xPzt6PwRXA1Q", - "secretKey": "DzMaU2L4OXl90uytwOmDXF2encN0Jf4Nxu2XkYqQ", - "bucketName": "irc-uat" - - }, "AWS": { "Region": "us-east-1", "EndPoint": "s3.us-east-1.amazonaws.com", "UseSSL": true, - "RoleArn": "arn:aws:iam::471112624751:role/sts_s3_upload", - "AccessKeyId": "AKIAW3MEAFJXWRCGSX5Z", - "SecretAccessKey": "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", - "BucketName": "ei-med-s3-lili-uat-store", - "ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com/", + "RoleArn": "arn:aws:iam::471112624751:role/lili_s3_access", + "AccessKeyId": "AKIAW3MEAFJXZ2TZK7GM", + "SecretAccessKey": "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", + "BucketName": "ei-med-s3-irc-store", + "ViewEndpoint": "https://ei-med-s3-irc-store.s3.amazonaws.com", "DurationSeconds": 7200 } }, - "BasicSystemConfig": { // 启用质控风险控制功能 "QCRiskControl": true, @@ -67,7 +72,6 @@ "OpenLoginLimit": true, "LoginMaxFailCount": 5, - "LoginFailLockMinutes": 30, "AutoLoginOutMinutes": 120, @@ -82,17 +86,17 @@ "TemplateType": 2, //MFA免验证发送天数 "UserMFAVerifyMinutes": 1440 - }, + "SystemEmailSendConfig": { "Port": 465, "Host": "smtp.qiye.aliyun.com", "Imap": "imap.qiye.aliyun.com", "ImapPort": 993, - "FromEmail": "uat@extimaging.com", - "FromName": "Uat IRC Imaging System", - "AuthorizationCode": "SHzyyl2021", - "SiteUrl": "http://irc.uat.extimaging.com/login", + "FromEmail": "irc@extimaging.com", + "FromName": "IRC Imaging System", + "AuthorizationCode": "ExtImg@2022", + "SiteUrl": "http://irc.extimaging.com/login", "PlatformName": "EICS", "PlatformNameCN": "展影云平台", @@ -104,15 +108,12 @@ "CompanyShortName": "Extensive Imaging", "CompanyShortNameCN": "展影医疗", "IsEnv_US": false, - "IsOpenErrorNoticeEmail": false, "EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", - "CronEmailDefaultCulture": "zh-CN", - "ErrorNoticeEmailList": [ "872297557@qq.com" ] + "CronEmailDefaultCulture": "zh-CN" }, - "SystemPacsConfig": { - "Port": "11116", - "IP": "101.132.253.119" + "Port": "11113", + "IP": "101.132.193.237" }, "RequestDuplicationOptions": { "IsEnabled": true, From 45db942f661783af647c8f4dc10453706da135b7 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 1 Jun 2026 15:37:01 +0800 Subject: [PATCH 04/25] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 8cac5500d..dab64d426 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -276,13 +276,17 @@ namespace IRaCIS.Core.Application.Service Log.Logger.Warning($"download_log.csv访视数量:{existVisits.Count}"); + Log.Logger.Warning($"csv排除前需要下载数量:{acturalDownList.Count}"); + acturalDownList = acturalDownList.Where(t => !existVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + Log.Logger.Warning($"csv排除后需要下载数量:{acturalDownList.Count}"); + } - acturalDownList = acturalDownList.Skip(startIndex - 1).Take(endIndex).ToList(); + acturalDownList = acturalDownList.Skip(startIndex - 1).Take(endIndex - startIndex + 1).ToList(); Log.Logger.Warning($"该区{startIndex}-{endIndex} 实际需要下载访视数量:{acturalDownList.Count}"); @@ -319,7 +323,7 @@ namespace IRaCIS.Core.Application.Service // 遍历 Series foreach (var seriesInfo in studyInfo.SeriesList) { - string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); + string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); // 创建 影像 文件夹 Directory.CreateDirectory(studyDicomFolderPath); @@ -333,7 +337,7 @@ namespace IRaCIS.Core.Application.Service downloadJobs.Add(new DownloadJob() { - Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_DICOM_{destinationPath}", + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_DICOM_{destinationPath}", Action = async () => { @@ -381,11 +385,11 @@ namespace IRaCIS.Core.Application.Service //建立压缩包 - string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}"); + string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}"); downloadJobs.Add(new DownloadJob() { - Name = $"{visitItem.SubjectCode}_{visitItem.VisitName}_Zip", + Name = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}_Zip", Action = async () => { @@ -411,14 +415,14 @@ namespace IRaCIS.Core.Application.Service //记录日志 downloadJobs.Add(new DownloadJob() { - Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_Finished", + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_Finished", Action = () => { DownloadLogger.Write( logFilePath: logFilePath, subjectCode: visitItem.SubjectCode, visitNum: visitItem.VisitNum, - visitName: visitItem.VisitName, + visitName: visitItem.VisitName.Trim(), message: "Success"); return Task.CompletedTask; From 9b6ddb0f1634e4e25a6ca0dbb42e909f27fe5bcb Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 1 Jun 2026 15:52:06 +0800 Subject: [PATCH 05/25] =?UTF-8?q?=E4=B8=8D=E5=8E=8B=E7=BC=A9=E6=89=93?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index dab64d426..ef8607b24 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -157,6 +157,7 @@ namespace IRaCIS.Core.Application.Service public class DownloadJob { + public bool IsZip { get; set; } public string Name { get; set; } @@ -367,7 +368,7 @@ namespace IRaCIS.Core.Application.Service foreach (var noneDicomStudy in visitItem.NoneDicomStudyList) { - string studyNoneDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); + string studyNoneDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); Directory.CreateDirectory(studyNoneDicomFolderPath); @@ -376,7 +377,7 @@ namespace IRaCIS.Core.Application.Service string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName)); //加入到下载任务里 - downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName}_NoneDICOM_{destinationPath}", Action = () => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath) }); + downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_NoneDICOM_{destinationPath}", Action = () => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath) }); //下载到当前目录 //await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath); @@ -391,6 +392,8 @@ namespace IRaCIS.Core.Application.Service { Name = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}_Zip", + IsZip = true, + Action = async () => { string zipPath = visitFolderPath + ".zip"; @@ -403,7 +406,7 @@ namespace IRaCIS.Core.Application.Service ZipFile.CreateFromDirectory( visitFolderPath, zipPath, - CompressionLevel.Fastest, + CompressionLevel.NoCompression, false); Directory.Delete(visitFolderPath, true); @@ -439,7 +442,18 @@ namespace IRaCIS.Core.Application.Service { try { - await job.Action(); + if (job.IsZip == false) + { + await job.Action(); + + } + else + { + _ = Task.Run(async () => + { + await job.Action(); + }); + } } catch (Exception ex) { From c0a383ef58fead25464c3a4f5770521cbb80ecdc Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Mon, 1 Jun 2026 16:41:40 +0800 Subject: [PATCH 06/25] =?UTF-8?q?MRIPDFF=20=E4=BF=AE=E6=94=B91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MRIPDFFAdvanceCalculateService.cs | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs index adb586594..735c53616 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs @@ -170,7 +170,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate - var dictionList = await _dictionaryRepository.Where(x => x.Parent.Code == "LiverSegmentation").OrderBy(x => x.ShowOrder).ToListAsync(); + var dictionList = await _dictionaryRepository.Where(x => x.Parent.Code == "Liver4Segmentation").OrderBy(x => x.ShowOrder).ToListAsync(); var tableQuestion = await _readingQuestionTrialRepository.Where(x => x.ReadingQuestionCriterionTrialId == taskinfo.TrialReadingCriterionId && x.LesionType == LesionType.FatFraction).FirstNotNullAsync(); @@ -297,7 +297,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate public async Task ReadingCalculate(ReadingCalculateDto inDto, List? calculateType = null) { - await this.CalculateAvg(inDto); + //await this.CalculateAvg(inDto); inDto = await _generalCalculateService.GetReadingCalculateDto(inDto.VisitTaskId); var needAddList = new List(); List calculateList = new List() @@ -456,54 +456,54 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate /// 计算平均值 /// /// - public async Task CalculateAvg(ReadingCalculateDto inDto) - { - // 脂肪分数的表格问题Id - var questionInfo = inDto.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction).FirstOrDefault(); + //public async Task CalculateAvg(ReadingCalculateDto inDto) + //{ + // // 脂肪分数的表格问题Id + // var questionInfo = inDto.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction).FirstOrDefault(); - List tableAnswers = new List(); + // List tableAnswers = new List(); - foreach (var item in questionInfo.TableRowInfoList) - { - var avg = item.TableQuestionList.Where(x => x.QuestionMark == QuestionMark.AverageValue).FirstOrDefault(); - var avgAnswer = string.Empty; - List questionMarks = new List() - { - QuestionMark.FirstMeasurement, - QuestionMark.SecondMeasurement, - QuestionMark.ThirdMeasurement, - QuestionMark.FourthMeasurement, - }; - var answers = item.TableQuestionList.Where(x => questionMarks.Contains(x.QuestionMark)).Select(x=>x.Answer).ToList(); - if (answers.Count() == 4 && !answers.Any(x => x.IsNullOrEmpty())) - { - var avgAnswernum= answers.Select(x=>x.IsNullOrEmptyReturn0()).Average(x=>x); - avgAnswer = decimal.Round(avgAnswernum, inDto.DigitPlaces, MidpointRounding.AwayFromZero).ToString("F" + inDto.DigitPlaces.ToString()); - } - if(item.TableQuestionList.Where(x => x.QuestionMark== QuestionMark.IsMeasurable).Select(x => x.Answer).FirstOrDefault().EqEnum(YesOrNoOrNa.No)) - { - avgAnswer = "NE"; - } - tableAnswers.Add(new ReadingTableQuestionAnswer() - { - Answer = avgAnswer, - VisitTaskId = inDto.VisitTaskId, - QuestionId = avg.QuestionId, - TableQuestionId = avg.TableQuestionId, - TrialId = inDto.TrialId, - RowIndex = avg.RowIndex, - RowId = avg.RowId, + // foreach (var item in questionInfo.TableRowInfoList) + // { + // var avg = item.TableQuestionList.Where(x => x.QuestionMark == QuestionMark.AverageValue).FirstOrDefault(); + // var avgAnswer = string.Empty; + // List questionMarks = new List() + // { + // QuestionMark.FirstMeasurement, + // QuestionMark.SecondMeasurement, + // QuestionMark.ThirdMeasurement, + // QuestionMark.FourthMeasurement, + // }; + // var answers = item.TableQuestionList.Where(x => questionMarks.Contains(x.QuestionMark)).Select(x=>x.Answer).ToList(); + // if (answers.Count() == 4 && !answers.Any(x => x.IsNullOrEmpty())) + // { + // var avgAnswernum= answers.Select(x=>x.IsNullOrEmptyReturn0()).Average(x=>x); + // avgAnswer = decimal.Round(avgAnswernum, inDto.DigitPlaces, MidpointRounding.AwayFromZero).ToString("F" + inDto.DigitPlaces.ToString()); + // } + // if(item.TableQuestionList.Where(x => x.QuestionMark== QuestionMark.IsMeasurable).Select(x => x.Answer).FirstOrDefault().EqEnum(YesOrNoOrNa.No)) + // { + // avgAnswer = "NE"; + // } + // tableAnswers.Add(new ReadingTableQuestionAnswer() + // { + // Answer = avgAnswer, + // VisitTaskId = inDto.VisitTaskId, + // QuestionId = avg.QuestionId, + // TableQuestionId = avg.TableQuestionId, + // TrialId = inDto.TrialId, + // RowIndex = avg.RowIndex, + // RowId = avg.RowId, - }); + // }); - await _readingTableQuestionAnswerRepository.BatchDeleteNoTrackingAsync(x => x.VisitTaskId == inDto.VisitTaskId && x.RowId == item.RowId && x.TableQuestionId == avg.TableQuestionId); - } + // await _readingTableQuestionAnswerRepository.BatchDeleteNoTrackingAsync(x => x.VisitTaskId == inDto.VisitTaskId && x.RowId == item.RowId && x.TableQuestionId == avg.TableQuestionId); + // } - await _readingTableQuestionAnswerRepository.AddRangeAsync(tableAnswers); - await _readingTableQuestionAnswerRepository.SaveChangesAsync(); + // await _readingTableQuestionAnswerRepository.AddRangeAsync(tableAnswers); + // await _readingTableQuestionAnswerRepository.SaveChangesAsync(); - } + //} @@ -523,12 +523,12 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) .SelectMany(x => x.TableRowInfoList).ToList(); - var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNotNullOrEmpty())).ToList(); + //var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNotNullOrEmpty())).ToList(); - if (tableQuestionList.Count() != 8) - { - throw new BusinessValidationFailedException(_localizer["MRIPDFF_AllNeedToBeMark"]); - } + //if (tableQuestionList.Count() != 4) + //{ + // throw new BusinessValidationFailedException(_localizer["MRIPDFF_AllNeedToBeMark"]); + //} try { From e11790ea9236d714e373503e29f53f79f54bfa82 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 1 Jun 2026 23:30:51 +0800 Subject: [PATCH 07/25] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=A4=9A=E5=B8=A7?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=EF=BC=8C=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appsettings.Prod_Event_IRC.json | 4 +- .../Helper/DicomDIRHelper.cs | 76 ++++++++++++ .../IRaCIS.Core.Application.xml | 4 +- .../Common/TrialImageDownloadService.cs | 112 +++++++++++++++--- 4 files changed, 175 insertions(+), 21 deletions(-) diff --git a/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json b/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json index 9c15f6a86..47936737a 100644 --- a/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json +++ b/IRaCIS.Core.API/appsettings.Prod_Event_IRC.json @@ -7,8 +7,8 @@ } }, "ConnectionStrings": { - "RemoteNew": "Server=10.10.10.49,1433;Database=Prod_IRC_pidr_restore;User ID=sa;Password=xc@123456;TrustServerCertificate=true", - "Hangfire": "Server=10.10.10.49,1433;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true" + "RemoteNew": "Server=101.132.193.237,1433;Database=Prod_IRC_pidr_restore;User ID=sa;Password=xc@123456;TrustServerCertificate=true", + "Hangfire": "Server=101.132.193.237,1433;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true" //"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true", //"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true" }, diff --git a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs index cba05d034..dd3b7c140 100644 --- a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs +++ b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs @@ -160,6 +160,82 @@ namespace IRaCIS.Core.Application.Helper } + public static async Task GenerateStudyDIR(List list, Dictionary dic,string dirSavePath) + { + var mappings = new List(); + int index = 1; + + var trialId = Guid.Empty; + + + var studyUid = list.FirstOrDefault()?.StudyInstanceUid; + + var dicomDir = new DicomDirectory(); + + foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber)) + { + var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID); + + if (dicomUid != null) + { + var ts = DicomTransferSyntax.Query(dicomUid); + + var dataset = new DicomDataset(ts) + { + { DicomTag.PatientID, item.PatientId ?? string.Empty }, + { DicomTag.PatientName, item.PatientName ?? string.Empty }, + { DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty }, + { DicomTag.PatientSex, item.PatientSex ?? string.Empty }, + + { DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty }, + { DicomTag.StudyID, item.StudyId ?? string.Empty }, + { DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty }, + { DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty }, + { DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty }, + { DicomTag.StudyDescription, item.StudyDescription ?? string.Empty }, + + { DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty }, + { DicomTag.Modality, item.Modality ?? string.Empty }, + { DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty }, + { DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty }, + { DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty }, + { DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty }, + + { DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty }, + { DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty }, + { DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty }, + { DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty }, + { DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty }, + { DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty }, + }; + + var dicomFile = new DicomFile(dataset); + + // 文件名递增格式:IM_00001, IM_00002, ... + string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位 + + mappings.Add($"{filename} => {item.InstanceId}"); + + dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last()); + + dicomDir.AddFile(dicomFile, filename); + + index++; + } + + + } + + //有实际的文件 + if (mappings.Count > 0) + { + // 保存 DICOMDIR 到临时文件 不能直接写入到流种 + await dicomDir.SaveAsync(dirSavePath); + } + + } + + public static StudyDIRInfo ReadDicomDIRInfo(DicomFile dicomFile) { diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index c0882ff60..cfc8e09af 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -1760,7 +1760,7 @@ - + 项目影像后台下载,不打包 @@ -6052,7 +6052,7 @@ - + 计算平均值 diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index ef8607b24..d3090eec7 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -7,6 +7,7 @@ using FellowOakDicom; using FellowOakDicom.Imaging; using FellowOakDicom.Imaging.Render; using FellowOakDicom.IO.Buffer; +using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.MassTransit.Command; using IRaCIS.Core.Application.ViewModel; @@ -45,7 +46,7 @@ namespace IRaCIS.Core.Application.Service /// /// [ApiExplorerSettings(GroupName = "Common")] - public class TrialImageDownloadService(IRepository _trialRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment, + public class TrialImageDownloadService(IRepository _trialRepository, IRepository _subjectVisitRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment, IRepository _studyRepository, IRepository _seriesRepository, IRepository _instanceRepository) : BaseService @@ -187,20 +188,25 @@ namespace IRaCIS.Core.Application.Service var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, + t.TrialCode, VisitList = t.SubjectVisitList.Where(t => t.VisitTaskList.Any(t => t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId != null && t.DoctorUserId != null)) //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) .Select(sv => new { + SubjectVisitId = sv.Id, TrialSiteCode = sv.TrialSite.TrialSiteCode, SubjectCode = sv.Subject.Code, VisitName = sv.VisitName, VisitNum = sv.VisitNum, StudyList = sv.StudyList.Select(u => new { + StudyId = u.Id, u.PatientId, u.StudyTime, u.StudyCode, + u.StudyInstanceUid, + u.StudyDIRPath, SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new { @@ -208,6 +214,7 @@ namespace IRaCIS.Core.Application.Service InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new { + InstanceId = k.Id, k.Path, k.IsEncapsulated, k.NumberOfFrames, @@ -301,6 +308,7 @@ namespace IRaCIS.Core.Application.Service var downloadJobs = new List(); + foreach (var visitItem in acturalDownList) { if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) @@ -308,32 +316,96 @@ namespace IRaCIS.Core.Application.Service continue; } - #region 处理 中心,受试者dicom non-dicom 文件夹层级 - - //var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode); - //if (!Directory.Exists(siteFolderPath)) - //{ - // Directory.CreateDirectory(siteFolderPath); - //} - - #endregion foreach (var studyInfo in visitItem.StudyList) { + + var dirDic = new Dictionary(); + + #region 生成DIR + + string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality))}"); + + // 创建 影像 文件夹 + Directory.CreateDirectory(studyDicomFolderPath); + + + if (!_instanceRepository.Where(t => t.IsReading && t.DicomSerie.IsReading) + .Where(t => visitItem.SubjectVisitId == t.SubjectVisitId).Any(c => c.TransferSytaxUID == string.Empty)) + { + var list = _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).SelectMany(t => t.StudyList) + .SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading && t.StudyId == studyInfo.StudyId)) + .Select(t => new StudyDIRInfo() + { + + DicomStudyId = t.DicomStudy.Id, + + PatientId = downloadInfo.TrialCode + "-" + t.DicomStudy.Subject.Code, + PatientName = t.DicomStudy.PatientName, + PatientBirthDate = t.DicomStudy.PatientBirthDate, + PatientSex = t.DicomStudy.PatientSex, + + StudyInstanceUid = t.StudyInstanceUid, + StudyId = t.DicomStudy.StudyId, + DicomStudyDate = t.DicomStudy.DicomStudyDate, + DicomStudyTime = t.DicomStudy.DicomStudyTime, + AccessionNumber = t.DicomStudy.AccessionNumber, + + StudyDescription = t.DicomStudy.Description, + + SeriesInstanceUid = t.DicomSerie.SeriesInstanceUid, + Modality = t.DicomSerie.Modality, + DicomSeriesDate = t.DicomSerie.DicomSeriesDate, + DicomSeriesTime = t.DicomSerie.DicomSeriesTime, + SeriesNumber = t.DicomSerie.SeriesNumber, + SeriesDescription = t.DicomSerie.Description, + + InstanceId = t.Id, + SopInstanceUid = t.SopInstanceUid, + SOPClassUID = t.SOPClassUID, + InstanceNumber = t.InstanceNumber, + MediaStorageSOPClassUID = t.MediaStorageSOPClassUID, + MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID, + TransferSytaxUID = t.TransferSytaxUID, + + }).ToList(); + + var pathInfo = await _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).Select(t => new { t.TrialId, t.SubjectId, VisitId = t.Id }).FirstNotNullAsync(); + + foreach (var group in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId })) + { + + var first = group.First(); + + + var dirPath = Path.Combine(studyDicomFolderPath, "DICOMDIR"); + + var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, dirPath)); + + + + + } + } + #endregion + // 遍历 Series foreach (var seriesInfo in studyInfo.SeriesList) { - string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); - // 创建 影像 文件夹 - Directory.CreateDirectory(studyDicomFolderPath); + // 遍历 InstancePathList foreach (var instanceInfo in seriesInfo.InstancePathList) { + + string destinationFolder = Path.Combine(studyDicomFolderPath, "IMAGE"); + + Directory.CreateDirectory(destinationFolder); + // 复制文件到相应的文件夹 - string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path)); + string destinationPath = Path.Combine(destinationFolder, dirDic[instanceInfo.InstanceId.ToString()]); downloadJobs.Add(new DownloadJob() @@ -342,13 +414,19 @@ namespace IRaCIS.Core.Application.Service Action = async () => { + await using var output = File.Create(destinationPath); if (instanceInfo.IsEncapsulated) { - await TryWriteMergedDicomAsync( - () => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path), - output); + var isSucess = await TryWriteMergedDicomAsync( + () => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path), + output); + + if (isSucess == false) + { + Log.Logger.Warning($"合并多帧失败: failed: {destinationFolder} {destinationPath}"); + } } else { From 9d4c69b39aa94dbb4f1c7f445732e916fc7b1db8 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 2 Jun 2026 01:17:01 +0800 Subject: [PATCH 08/25] =?UTF-8?q?=E9=80=9A=E8=BF=87Excel=20=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E4=BF=A1=E6=81=AF=E8=BF=9B=E8=A1=8C=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 386 +++++++++++++++++- 1 file changed, 385 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index d3090eec7..eaad74bcb 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -7,6 +7,7 @@ using FellowOakDicom; using FellowOakDicom.Imaging; using FellowOakDicom.Imaging.Render; using FellowOakDicom.IO.Buffer; +using Hangfire.Common; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.MassTransit.Command; @@ -19,7 +20,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; -using NPOI.Util; using Org.BouncyCastle.Utilities.Zlib; using SharpCompress.Common; using System; @@ -34,6 +34,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using static IRaCIS.Core.Application.Service.TestService; +using static IRaCIS.Core.Application.Service.TrialImageDownloadService; using static IRaCIS.Core.Domain.Share.StaticData; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; @@ -165,6 +166,389 @@ namespace IRaCIS.Core.Application.Service public Func Action { get; set; } } + [HttpPost] + [AllowAnonymous] + public async Task DownloadExcelTrialImage(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 downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()); + + var downloadJobs = new List(); + + foreach (var downloadVisit in downloadVisits) + { + var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new + { + t.ResearchProgramNo, + t.TrialCode, + + VisitList = t.SubjectVisitList.Where(t => t.VisitName.Trim() == downloadVisit.VisitName.Trim() && t.Subject.Code.Trim() == downloadVisit.SubjectCode.Trim() && t.VisitNum == downloadVisit.VisitNum) + .Select(sv => new + { + SubjectVisitId = sv.Id, + TrialSiteCode = sv.TrialSite.TrialSiteCode, + SubjectCode = sv.Subject.Code, + VisitName = sv.VisitName, + VisitNum = sv.VisitNum, + StudyList = sv.StudyList.Select(u => new + { + StudyId = u.Id, + u.PatientId, + u.StudyTime, + u.StudyCode, + u.StudyInstanceUid, + u.StudyDIRPath, + + SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new + { + z.Modality, + + InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new + { + InstanceId = k.Id, + k.Path, + k.IsEncapsulated, + k.NumberOfFrames, + }).ToList() + }) + + }).ToList(), + + NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new + { + nd.Modality, + nd.StudyCode, + nd.ImageDate, + + FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new + { + file.FileName, + file.Path, + file.FileType + }).ToList() + }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() + + }).FirstOrDefault(); + + if (downloadInfo == null) + { + return ResponseOutput.Ok(); + } + + #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.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && + old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) + { + continue; + } + + } + + + #endregion + + + + foreach (var visitItem in downloadInfo.VisitList) + { + if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) + { + continue; + } + + + foreach (var studyInfo in visitItem.StudyList) + { + + var dirDic = new Dictionary(); + + #region 生成DIR + + string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality))}"); + + // 创建 影像 文件夹 + Directory.CreateDirectory(studyDicomFolderPath); + + + if (!_instanceRepository.Where(t => t.IsReading && t.DicomSerie.IsReading) + .Where(t => visitItem.SubjectVisitId == t.SubjectVisitId).Any(c => c.TransferSytaxUID == string.Empty)) + { + var list = _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).SelectMany(t => t.StudyList) + .SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading && t.StudyId == studyInfo.StudyId)) + .Select(t => new StudyDIRInfo() + { + + DicomStudyId = t.DicomStudy.Id, + + PatientId = downloadInfo.TrialCode + "-" + t.DicomStudy.Subject.Code, + PatientName = t.DicomStudy.PatientName, + PatientBirthDate = t.DicomStudy.PatientBirthDate, + PatientSex = t.DicomStudy.PatientSex, + + StudyInstanceUid = t.StudyInstanceUid, + StudyId = t.DicomStudy.StudyId, + DicomStudyDate = t.DicomStudy.DicomStudyDate, + DicomStudyTime = t.DicomStudy.DicomStudyTime, + AccessionNumber = t.DicomStudy.AccessionNumber, + + StudyDescription = t.DicomStudy.Description, + + SeriesInstanceUid = t.DicomSerie.SeriesInstanceUid, + Modality = t.DicomSerie.Modality, + DicomSeriesDate = t.DicomSerie.DicomSeriesDate, + DicomSeriesTime = t.DicomSerie.DicomSeriesTime, + SeriesNumber = t.DicomSerie.SeriesNumber, + SeriesDescription = t.DicomSerie.Description, + + InstanceId = t.Id, + SopInstanceUid = t.SopInstanceUid, + SOPClassUID = t.SOPClassUID, + InstanceNumber = t.InstanceNumber, + MediaStorageSOPClassUID = t.MediaStorageSOPClassUID, + MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID, + TransferSytaxUID = t.TransferSytaxUID, + + }).ToList(); + + var pathInfo = await _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).Select(t => new { t.TrialId, t.SubjectId, VisitId = t.Id }).FirstNotNullAsync(); + + foreach (var group in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId })) + { + + var first = group.First(); + + + var dirPath = Path.Combine(studyDicomFolderPath, "DICOMDIR"); + + var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, dirPath)); + + + + + } + } + #endregion + + // 遍历 Series + foreach (var seriesInfo in studyInfo.SeriesList) + { + + + + // 遍历 InstancePathList + foreach (var instanceInfo in seriesInfo.InstancePathList) + { + + string destinationFolder = Path.Combine(studyDicomFolderPath, "IMAGE"); + + Directory.CreateDirectory(destinationFolder); + + // 复制文件到相应的文件夹 + string destinationPath = Path.Combine(destinationFolder, dirDic[instanceInfo.InstanceId.ToString()]); + + + downloadJobs.Add(new DownloadJob() + { + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_DICOM_{destinationPath}", + + Action = async () => + { + + await using var output = File.Create(destinationPath); + + if (instanceInfo.IsEncapsulated) + { + var isSucess = await TryWriteMergedDicomAsync( + () => _oSSService.GetStreamFromOSSAsync(instanceInfo.Path), + output); + + if (isSucess == false) + { + Log.Logger.Warning($"合并多帧失败: failed: {destinationFolder} {destinationPath}"); + } + } + else + { + await using var input = + await _oSSService.GetStreamFromOSSAsync(instanceInfo.Path); + + await input.CopyToAsync(output); + } + } + }); + + } + } + + + } + + foreach (var noneDicomStudy in visitItem.NoneDicomStudyList) + { + string studyNoneDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); + + Directory.CreateDirectory(studyNoneDicomFolderPath); + + foreach (var file in noneDicomStudy.FileList) + { + string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName)); + + //加入到下载任务里 + downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_NoneDICOM_{destinationPath}", Action = () => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath) }); + + //下载到当前目录 + //await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath); + } + } + + + //建立压缩包 + string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}"); + + downloadJobs.Add(new DownloadJob() + { + Name = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}_Zip", + + IsZip = true, + + Action = async () => + { + Log.Logger.Warning($" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); + + string zipPath = visitFolderPath + ".zip"; + + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + ZipFile.CreateFromDirectory( + visitFolderPath, + zipPath, + CompressionLevel.NoCompression, + false); + + Directory.Delete(visitFolderPath, true); + + await Task.CompletedTask; + } + }); + + //记录日志 + downloadJobs.Add(new DownloadJob() + { + Name = $"{visitItem.SubjectCode}_{visitItem.VisitNum}_{visitItem.VisitName.Trim()}_Finished", + Action = () => + { + DownloadLogger.Write( + logFilePath: logFilePath, + subjectCode: visitItem.SubjectCode, + visitNum: visitItem.VisitNum, + visitName: visitItem.VisitName.Trim(), + message: "Success"); + + return Task.CompletedTask; + } + }); + } + + + + + + + + + } + + + + + + #region 异步方式处理 + + int totalCount = downloadJobs.Count; + int downloadedCount = 0; + + Log.Logger.Warning($"开始下载总数: {totalCount}"); + foreach (var job in downloadJobs) + { + try + { + if (job.IsZip == false) + { + await job.Action(); + + } + else + { + _ = Task.Run(async () => + { + await job.Action(); + }); + } + } + catch (Exception ex) + { + Log.Logger.Error($"{job.Name}下载失败: {ex.Message}"); + } + + downloadedCount++; + + // 每处理50个,输出一次进度(或最后一个时也输出) + if (downloadedCount % 50 == 0 || downloadedCount == totalCount) + { + + Log.Logger.Warning($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + + } + } + #endregion + + + return ResponseOutput.Ok(); + + + + + } + + /// /// 后端api swagger 下载项目影像 /// From 0508b8ffb38daba4e7320b3fbf3ba43a6d095f71 Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Tue, 2 Jun 2026 09:27:41 +0800 Subject: [PATCH 09/25] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.Application/IRaCIS.Core.Application.xml | 8 ++++---- IRaCIS.Core.Application/Service/Common/MailService.cs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index c0882ff60..fa54d6d24 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -6052,7 +6052,7 @@ - + 计算平均值 @@ -17496,17 +17496,17 @@ - 质疑 + ���� - 一致性核查 + һ���Ժ˲� - 复制 + ���� diff --git a/IRaCIS.Core.Application/Service/Common/MailService.cs b/IRaCIS.Core.Application/Service/Common/MailService.cs index 50a32982a..3e54c32e9 100644 --- a/IRaCIS.Core.Application/Service/Common/MailService.cs +++ b/IRaCIS.Core.Application/Service/Common/MailService.cs @@ -984,7 +984,8 @@ namespace IRaCIS.Core.Application.Service var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage), - sysUserInfo.FullName + sysUserInfo.FullName, + _systemEmailConfig.PlatformName ); return (topicStr, htmlBodyStr); From 25a18e8366619a5c988aad95ec71185573417c09 Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Tue, 2 Jun 2026 10:04:42 +0800 Subject: [PATCH 10/25] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/TrialSiteUser/DTO/TrialViewModel.cs | 2 ++ IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialViewModel.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialViewModel.cs index 613993009..9fc0f15d2 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialViewModel.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialViewModel.cs @@ -88,6 +88,8 @@ namespace IRaCIS.Application.Contracts public string CriterionName { get; set; } = string.Empty; + public DateTime CreateTime { get; set; } + public List TrialObjectNameList { get; set; } = new List(); } diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs index 3dd75ddbd..362ea4ddc 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs @@ -144,7 +144,8 @@ namespace IRaCIS.Core.Application.Service .WhereIf(!other.Contains(_userInfo.UserTypeEnumInt), t => t.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.IsDeleted == false && t.TrialUserRoleList.Any(t => t.UserId == _userInfo.UserRoleId && t.IsDeleted == false)) && t.IsDeleted == false && t.TrialStatusStr == StaticData.TrialState.TrialOngoing) - .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); + .ProjectTo(_mapper.ConfigurationProvider) + .OrderByDescending(x=>x.CreateTime).ToListAsync(); } From 199884220046d9510f0447fcdf9597ee74a772de Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Tue, 2 Jun 2026 10:38:15 +0800 Subject: [PATCH 11/25] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MRIPDFFAdvanceCalculateService.cs | 57 +++++++++++-------- IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs | 5 ++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs index 735c53616..313d1bb00 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs @@ -517,11 +517,11 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate public async Task VerifyVisitTaskQuestions(VerifyVisitTaskQuestionsInDto inDto) { - ReadingCalculateDto readingData = await _generalCalculateService.GetReadingCalculateDto(inDto.VisitTaskId); + //ReadingCalculateDto readingData = await _generalCalculateService.GetReadingCalculateDto(inDto.VisitTaskId); - var markList = await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId).ToListAsync(); - var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) - .SelectMany(x => x.TableRowInfoList).ToList(); + //var markList = await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId).ToListAsync(); + //var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) + // .SelectMany(x => x.TableRowInfoList).ToList(); //var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNotNullOrEmpty())).ToList(); @@ -530,32 +530,39 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate // throw new BusinessValidationFailedException(_localizer["MRIPDFF_AllNeedToBeMark"]); //} - try - { - List questionMarkList = new List() { QuestionMark.FirstMeasurement, QuestionMark.SecondMeasurement, QuestionMark.ThirdMeasurement, QuestionMark.FourthMeasurement }; - var measuredValueList = rowInfo.SelectMany(x => x.TableQuestionList).Where(x =>x.Answer.IsNotNullOrEmpty()&& questionMarkList.Contains(x.QuestionMark)).Select(x => decimal.Parse(x.Answer)).ToList(); - if (measuredValueList.Any(x => x > 100)) - { - throw new BusinessValidationFailedException(_localizer["MRIPDFF_MeasurementGT100"]); - } - } - catch (Exception) - { + var instanceCount = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId && x.InstanceId != null).Select(x => x.InstanceId).Distinct().CountAsync(); - throw new BusinessValidationFailedException(_localizer["MRIPDFF_MeasurementGT100"]); + if (instanceCount > 1) + { + throw new BusinessValidationFailedException(_localizer["MRIPDFFAdvance_MarkMustBeSameInstance"]); } - var notableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.IsMeasurable && x.Answer.EqEnum(YesOrNoOrNa.No))).ToList(); + //try + //{ + // List questionMarkList = new List() { QuestionMark.FirstMeasurement, QuestionMark.SecondMeasurement, QuestionMark.ThirdMeasurement, QuestionMark.FourthMeasurement }; + // var measuredValueList = rowInfo.SelectMany(x => x.TableQuestionList).Where(x =>x.Answer.IsNotNullOrEmpty()&& questionMarkList.Contains(x.QuestionMark)).Select(x => decimal.Parse(x.Answer)).ToList(); + // if (measuredValueList.Any(x => x > 100)) + // { + // throw new BusinessValidationFailedException(_localizer["MRIPDFF_MeasurementGT100"]); + // } + //} + //catch (Exception) + //{ + + // throw new BusinessValidationFailedException(_localizer["MRIPDFF_MeasurementGT100"]); + //} + + //var notableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.IsMeasurable && x.Answer.EqEnum(YesOrNoOrNa.No))).ToList(); - foreach (var item in notableQuestionList) - { - if (markList.Any(x => x.RowId == item.RowId && x.MeasureData.IsNotNullOrEmpty())) - { - throw new BusinessValidationFailedException(_localizer["MRIPDFF_NeedClearMark"]); - } - } - + //foreach (var item in notableQuestionList) + //{ + // if (markList.Any(x => x.RowId == item.RowId && x.MeasureData.IsNotNullOrEmpty())) + // { + // throw new BusinessValidationFailedException(_localizer["MRIPDFF_NeedClearMark"]); + // } + //} + } } } diff --git a/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs b/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs index d9ff423a0..8899f8bd1 100644 --- a/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs +++ b/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs @@ -3237,6 +3237,11 @@ namespace IRaCIS.Core.Domain.Share /// 保存eCRF /// SaveEICRFQuestions = 12, + + /// + /// 保存Advance肝脏分段 + /// + SaveAdvanceLiverSegments = 13, } /// From de19c5bc32ef33de80e56e20cc84a11946ef01d6 Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Tue, 2 Jun 2026 10:55:47 +0800 Subject: [PATCH 12/25] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E5=8F=91=E9=80=81=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Management/UserService.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/Management/UserService.cs b/IRaCIS.Core.Application/Service/Management/UserService.cs index 67de9f202..c8cdf0cd7 100644 --- a/IRaCIS.Core.Application/Service/Management/UserService.cs +++ b/IRaCIS.Core.Application/Service/Management/UserService.cs @@ -442,7 +442,16 @@ namespace IRaCIS.Core.Application.Service await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, ActionIdentityUserId = identityUserId, ActionUserName = _userInfo.UserName, TargetIdentityUserId = identityUserId, OptType = UserOptType.UnloginModifyPasswoed }, true); - await _mailVerificationService.AfterUserModifyPasswordSendEmailAsync(identityUserId); + try + { + await _mailVerificationService.AfterUserModifyPasswordSendEmailAsync(identityUserId); + } + catch (Exception) + { + + + } + var find = await _identityUserRepository.FindAsync(identityUserId); From 5e345418fb10ba8e8077705ab264c30f163e3188 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 2 Jun 2026 14:13:17 +0800 Subject: [PATCH 13/25] =?UTF-8?q?=E5=88=86=E9=85=8D=E9=98=85=E7=89=87?= =?UTF-8?q?=E4=BA=BAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IRaCIS.Core.Application.xml | 6 +- .../Allocation/VisitTaskHelpeService.cs | 8 +- IRaCIS.Core.Application/TestService.cs | 77 ++++++++++++++++++- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 49858298f..cfc8e09af 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -17496,17 +17496,17 @@ - ���� + 质疑 - һ���Ժ˲� + 一致性核查 - ���� + 复制 diff --git a/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs b/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs index ccdae5dfa..5a48de460 100644 --- a/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs +++ b/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs @@ -873,7 +873,7 @@ namespace IRaCIS.Core.Application.Service { arm1.TaskAllocationState = TaskAllocationState.Allocated; arm1.AllocateTime = DateTime.Now; - arm1.DoctorUserId = task1.DoctorUserId; + arm1.DoctorUserId = assignConfigList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm1)!.DoctorUserId; arm1.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget); } @@ -913,7 +913,7 @@ namespace IRaCIS.Core.Application.Service { taskOne.TaskAllocationState = TaskAllocationState.Allocated; taskOne.AllocateTime = DateTime.Now; - taskOne.DoctorUserId = task1!.DoctorUserId; + taskOne.DoctorUserId = assignConfigList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm1)!.DoctorUserId; taskOne.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget); } @@ -944,7 +944,7 @@ namespace IRaCIS.Core.Application.Service { arm2.TaskAllocationState = TaskAllocationState.Allocated; arm2.AllocateTime = DateTime.Now; - arm2.DoctorUserId = task2.DoctorUserId; + arm2.DoctorUserId = assignConfigList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm2)!.DoctorUserId; arm2.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget); } @@ -986,7 +986,7 @@ namespace IRaCIS.Core.Application.Service { taskTwo.TaskAllocationState = TaskAllocationState.Allocated; taskTwo.AllocateTime = DateTime.Now; - taskTwo.DoctorUserId = task2!.DoctorUserId; + taskTwo.DoctorUserId = assignConfigList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm2)!.DoctorUserId; taskTwo.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget); } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 1d6230106..caf6c12fd 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -209,7 +209,7 @@ namespace IRaCIS.Core.Application.Service } [AllowAnonymous] - public async Task ExtralUndownloadImages() + public async Task ExtralUndownloadImages(Guid trialId) { var newVisits = MiniExcel.Query(@"C:\Users\PC\Desktop\New.xlsx").ToList(); @@ -219,7 +219,7 @@ namespace IRaCIS.Core.Application.Service foreach (var item in newVisits) { - if (oldVisits.Any(t => t.VisitNum == item.VisitNum && t.SubjectCode == item.SubjectCode)) + if (oldVisits.Any(t => t.VisitNum == item.VisitNum && t.SubjectCode.Trim() == item.SubjectCode.Trim())) { continue; } @@ -231,7 +231,78 @@ namespace IRaCIS.Core.Application.Service string exportPath = @$"C:\Users\PC\Desktop\newDownload.xlsx"; - MiniExcel.SaveAs(exportPath, downloadVisit); + //MiniExcel.SaveAs(exportPath, downloadVisit); + + + #region 数据库查询 + var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new + { + t.ResearchProgramNo, + t.TrialCode, + + VisitList = t.SubjectVisitList.Where(t => t.VisitTaskList.Any(t => t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId != null && t.DoctorUserId != null)) + //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) + .Select(sv => new + { + SubjectVisitId = sv.Id, + TrialSiteCode = sv.TrialSite.TrialSiteCode, + SubjectCode = sv.Subject.Code, + VisitName = sv.VisitName, + VisitNum = sv.VisitNum, + StudyList = sv.StudyList.Select(u => new + { + StudyId = u.Id, + u.PatientId, + u.StudyTime, + u.StudyCode, + u.StudyInstanceUid, + u.StudyDIRPath, + + SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new + { + z.Modality, + + InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new + { + InstanceId = k.Id, + k.Path, + k.IsEncapsulated, + k.NumberOfFrames, + }).ToList() + }) + + }).ToList(), + + NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new + { + nd.Modality, + nd.StudyCode, + nd.ImageDate, + + FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new + { + file.FileName, + file.Path, + file.FileType + }).ToList() + }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() + + }).FirstOrDefault(); + + var acturalDownList = downloadInfo.VisitList.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && +old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + + + var diffList = downloadVisit.Where(t => !acturalDownList.Any(old => old.SubjectCode.Trim() == t.SubjectCode.Trim() && +old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + + + + string diffPath = @$"C:\Users\PC\Desktop\diff.xlsx"; + MiniExcel.SaveAs(diffPath, diffList); + + #endregion return ResponseOutput.Ok(downloadVisit); } From 967c0114d8b228a6e219b7c2f00edcbaae4a3c7a Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 2 Jun 2026 15:03:00 +0800 Subject: [PATCH 14/25] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E8=AE=BF=E8=A7=86=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.Application/TestService.cs | 116 +++++++++++++------------ 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index caf6c12fd..34e6f3364 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -206,6 +206,14 @@ namespace IRaCIS.Core.Application.Service public Guid? SubjectVisitId { get; set; } public decimal VisitNum { get; set; } public string VisitName { get; set; } + + public string R1 { get; set; } + + public string R1阅片状态 { get; set; } + + public string R2 { get; set; } + + public string R2阅片状态 { get; set; } } [AllowAnonymous] @@ -231,76 +239,76 @@ namespace IRaCIS.Core.Application.Service string exportPath = @$"C:\Users\PC\Desktop\newDownload.xlsx"; - //MiniExcel.SaveAs(exportPath, downloadVisit); + MiniExcel.SaveAs(exportPath, downloadVisit); #region 数据库查询 - var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new - { - t.ResearchProgramNo, - t.TrialCode, +// var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new +// { +// t.ResearchProgramNo, +// t.TrialCode, - VisitList = t.SubjectVisitList.Where(t => t.VisitTaskList.Any(t => t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId != null && t.DoctorUserId != null)) - //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) - .Select(sv => new - { - SubjectVisitId = sv.Id, - TrialSiteCode = sv.TrialSite.TrialSiteCode, - SubjectCode = sv.Subject.Code, - VisitName = sv.VisitName, - VisitNum = sv.VisitNum, - StudyList = sv.StudyList.Select(u => new - { - StudyId = u.Id, - u.PatientId, - u.StudyTime, - u.StudyCode, - u.StudyInstanceUid, - u.StudyDIRPath, +// VisitList = t.SubjectVisitList.Where(t => t.VisitTaskList.Any(t => t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId != null && t.DoctorUserId != null)) +// //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) +// .Select(sv => new +// { +// SubjectVisitId = sv.Id, +// TrialSiteCode = sv.TrialSite.TrialSiteCode, +// SubjectCode = sv.Subject.Code, +// VisitName = sv.VisitName, +// VisitNum = sv.VisitNum, +// StudyList = sv.StudyList.Select(u => new +// { +// StudyId = u.Id, +// u.PatientId, +// u.StudyTime, +// u.StudyCode, +// u.StudyInstanceUid, +// u.StudyDIRPath, - SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new - { - z.Modality, +// SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new +// { +// z.Modality, - InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new - { - InstanceId = k.Id, - k.Path, - k.IsEncapsulated, - k.NumberOfFrames, - }).ToList() - }) +// InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new +// { +// InstanceId = k.Id, +// k.Path, +// k.IsEncapsulated, +// k.NumberOfFrames, +// }).ToList() +// }) - }).ToList(), +// }).ToList(), - NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new - { - nd.Modality, - nd.StudyCode, - nd.ImageDate, +// NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new +// { +// nd.Modality, +// nd.StudyCode, +// nd.ImageDate, - FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new - { - file.FileName, - file.Path, - file.FileType - }).ToList() - }).ToList() - }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() +// FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new +// { +// file.FileName, +// file.Path, +// file.FileType +// }).ToList() +// }).ToList() +// }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() - }).FirstOrDefault(); +// }).FirstOrDefault(); - var acturalDownList = downloadInfo.VisitList.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && -old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); +// var acturalDownList = downloadInfo.VisitList.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && +//old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); - var diffList = downloadVisit.Where(t => !acturalDownList.Any(old => old.SubjectCode.Trim() == t.SubjectCode.Trim() && -old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); +// var diffList = downloadVisit.Where(t => !acturalDownList.Any(old => old.SubjectCode.Trim() == t.SubjectCode.Trim() && +//old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); - string diffPath = @$"C:\Users\PC\Desktop\diff.xlsx"; - MiniExcel.SaveAs(diffPath, diffList); +// string diffPath = @$"C:\Users\PC\Desktop\diff.xlsx"; +// MiniExcel.SaveAs(diffPath, diffList); #endregion From 432bf1ae122e1cc81eff12d9a2d93e8f92e7ac9f Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 2 Jun 2026 16:03:55 +0800 Subject: [PATCH 15/25] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=88=A0=E9=99=A4?= =?UTF-8?q?=EF=BC=8C=E5=AF=BC=E8=87=B4=E5=8F=82=E4=B8=8E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/TrialSiteUser/TrialMaintenanceService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs index 47d6a8612..8acfa26dc 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs @@ -35,7 +35,7 @@ namespace IRaCIS.Core.Application.Service [HttpPost] public async Task>> GetUserJoinedTrialList(IdentityUserJoinedTrialQuery inQuery) { - var list = await _trialIdentityUserRepository.Where(t => t.IdentityUserId == inQuery.IdentityUserId, false, true) + var list = await _trialIdentityUserRepository.Where(t => t.IdentityUserId == inQuery.IdentityUserId && t.Trial.IsDeleted==false, false, true) .WhereIf(!string.IsNullOrEmpty(inQuery.TrialCode), o => o.Trial.TrialCode.Contains(inQuery.TrialCode)) .WhereIf(!string.IsNullOrEmpty(inQuery.ResearchProgramNo), o => o.Trial.ResearchProgramNo.Contains(inQuery.ResearchProgramNo)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.ExperimentName), o => o.Trial.ExperimentName.Contains(inQuery.ExperimentName)) From 7a30fd0f28095e54561956f485d29da1ad67bd60 Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Tue, 2 Jun 2026 16:25:41 +0800 Subject: [PATCH 16/25] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MRIPDFFAdvanceCalculateService.cs | 22 +++++++++---------- .../MRIPDFFCalculateService.cs | 8 +++---- IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs index 313d1bb00..06cec87ae 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFAdvanceCalculateService.cs @@ -408,7 +408,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate var tableQuestionList = rowInfo.Where(x=>x.TableQuestionList.Any(x=>x.QuestionMark== QuestionMark.IsMeasurable && x.Answer.EqEnum(YesOrNoOrNa.Yes))).SelectMany(x => x.TableQuestionList).ToList(); - if (rowInfo.Any(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNullOrEmpty()))) + if (rowInfo.Any(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.MRIPDFF && x.Answer.IsNullOrEmpty()))) { return string.Empty; } @@ -416,7 +416,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate { return "NE"; } - result = tableQuestionList.Where(x => x.QuestionMark == QuestionMark.AverageValue).Average(x => x.Answer.IsNullOrEmptyReturn0()); + result = tableQuestionList.Where(x => x.QuestionMark == QuestionMark.MRIPDFF).Average(x => x.Answer.IsNullOrEmptyReturn0()); return decimal.Round(result, inDto.DigitPlaces, MidpointRounding.AwayFromZero).ToString("F" + inDto.DigitPlaces.ToString()); ; } @@ -517,18 +517,18 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate public async Task VerifyVisitTaskQuestions(VerifyVisitTaskQuestionsInDto inDto) { - //ReadingCalculateDto readingData = await _generalCalculateService.GetReadingCalculateDto(inDto.VisitTaskId); + ReadingCalculateDto readingData = await _generalCalculateService.GetReadingCalculateDto(inDto.VisitTaskId); - //var markList = await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId).ToListAsync(); - //var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) - // .SelectMany(x => x.TableRowInfoList).ToList(); + var markList = await _readingTaskQuestionMarkRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId).ToListAsync(); + var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) + .SelectMany(x => x.TableRowInfoList).ToList(); - //var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNotNullOrEmpty())).ToList(); + var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.MRIPDFF && x.Answer.IsNotNullOrEmpty())).ToList(); - //if (tableQuestionList.Count() != 4) - //{ - // throw new BusinessValidationFailedException(_localizer["MRIPDFF_AllNeedToBeMark"]); - //} + if (tableQuestionList.Count() != 4) + { + throw new BusinessValidationFailedException(_localizer["MRIPDFF_AllNeedToBeMark"]); + } var instanceCount = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == inDto.VisitTaskId && x.InstanceId != null).Select(x => x.InstanceId).Distinct().CountAsync(); diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFCalculateService.cs index 0b564dfd5..cbd2039a9 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/MRIPDFFCalculateService.cs @@ -408,7 +408,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate var tableQuestionList = rowInfo.Where(x=>x.TableQuestionList.Any(x=>x.QuestionMark== QuestionMark.IsMeasurable && x.Answer.EqEnum(YesOrNoOrNa.Yes))).SelectMany(x => x.TableQuestionList).ToList(); - if (rowInfo.Any(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNullOrEmpty()))) + if (rowInfo.Any(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.MRIPDFF && x.Answer.IsNullOrEmpty()))) { return string.Empty; } @@ -416,7 +416,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate { return "NE"; } - result = tableQuestionList.Where(x => x.QuestionMark == QuestionMark.AverageValue).Average(x => x.Answer.IsNullOrEmptyReturn0()); + result = tableQuestionList.Where(x => x.QuestionMark == QuestionMark.MRIPDFF).Average(x => x.Answer.IsNullOrEmptyReturn0()); return decimal.Round(result, inDto.DigitPlaces, MidpointRounding.AwayFromZero).ToString("F" + inDto.DigitPlaces.ToString()); ; } @@ -466,7 +466,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate foreach (var item in questionInfo.TableRowInfoList) { - var avg = item.TableQuestionList.Where(x => x.QuestionMark == QuestionMark.AverageValue).FirstOrDefault(); + var avg = item.TableQuestionList.Where(x => x.QuestionMark == QuestionMark.MRIPDFF).FirstOrDefault(); var avgAnswer = string.Empty; List questionMarks = new List() { @@ -522,7 +522,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate var rowInfo = readingData.QuestionInfo.Where(x => x.LesionType == LesionType.FatFraction) .SelectMany(x => x.TableRowInfoList).ToList(); - var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.AverageValue && x.Answer.IsNotNullOrEmpty())).ToList(); + var tableQuestionList = rowInfo.Where(x => x.TableQuestionList.Any(x => x.QuestionMark == QuestionMark.MRIPDFF && x.Answer.IsNotNullOrEmpty())).ToList(); if (tableQuestionList.Count() != 8) { diff --git a/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs b/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs index 8899f8bd1..6b8592eb7 100644 --- a/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs +++ b/IRaCIS.Core.Domain.Share/Reading/ReadEnum.cs @@ -2514,7 +2514,7 @@ namespace IRaCIS.Core.Domain.Share /// /// 平均值 /// - AverageValue = 1104, + MRIPDFF = 1104, /// /// 是否可测量 From 9a0859f915b6aa60285c9b6a1c1c8227d729100c Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 2 Jun 2026 17:23:27 +0800 Subject: [PATCH 17/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/TrialSiteUser/TrialMaintenanceService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs index 8acfa26dc..6050eed50 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialMaintenanceService.cs @@ -35,6 +35,9 @@ namespace IRaCIS.Core.Application.Service [HttpPost] public async Task>> GetUserJoinedTrialList(IdentityUserJoinedTrialQuery inQuery) { + + var defalutSortArray = new string[] { nameof(IdentityUserJoinedTrialView.TrialCreateTime) + " desc" }; + var list = await _trialIdentityUserRepository.Where(t => t.IdentityUserId == inQuery.IdentityUserId && t.Trial.IsDeleted==false, false, true) .WhereIf(!string.IsNullOrEmpty(inQuery.TrialCode), o => o.Trial.TrialCode.Contains(inQuery.TrialCode)) .WhereIf(!string.IsNullOrEmpty(inQuery.ResearchProgramNo), o => o.Trial.ResearchProgramNo.Contains(inQuery.ResearchProgramNo)) @@ -62,7 +65,7 @@ namespace IRaCIS.Core.Application.Service UpdateTime = t.UpdateTime, }).ToList(), - }).ToPagedListAsync(inQuery); + }).ToPagedListAsync(inQuery, defalutSortArray); var info = await _identityUserRepository.Where(t => t.Id == inQuery.IdentityUserId).Select(t => new { t.CreateTime, t.UserCeateSource, t.Trial.ResearchProgramNo, t.Trial.ExperimentName, t.Trial.TrialCode }).FirstOrDefaultAsync(); From 2969306d1e331d114d532eed86a00f17f6157a0f Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 11:07:04 +0800 Subject: [PATCH 18/25] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=A4=9A=E5=B8=A7?= =?UTF-8?q?=E4=B8=8D=E4=BF=9D=E7=95=99=E5=81=8F=E7=A7=BB=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 29 +++++++++++++++---- .../Service/ImageAndDoc/StudyService.cs | 6 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index eaad74bcb..6ba580e20 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -61,8 +61,24 @@ namespace IRaCIS.Core.Application.Service try { await using var source = await sourceFactory(); + + // 【关键修复】将 OSS 流缓冲到 MemoryStream + using var bufferedStream = new MemoryStream(); + + if (source.CanSeek) + { + source.Position = 0; + + } + else + { + // 完全复制到内存流 + await source.CopyToAsync(bufferedStream); + bufferedStream.Position = 0; // 重置位置 + } + // 如果你是从 stream 打开 - var dicomFile = await DicomFile.OpenAsync(source); + var dicomFile = await DicomFile.OpenAsync(source.CanSeek ? source : bufferedStream); //获取像素是否为封装形式 var syntax = dicomFile.Dataset.InternalTransferSyntax; @@ -70,11 +86,14 @@ namespace IRaCIS.Core.Application.Service //对于封装像素的文件做转换 if (syntax.IsEncapsulated) { + #region 开始方式 + // 获取 Pixel Data 标签 var pixelData = DicomPixelData.Create(dicomFile.Dataset); // 创建一个新的片段序列 var newFragments = new DicomOtherByteFragment(DicomTag.PixelData); + // 获取每帧数据并封装为单独的片段 for (int n = 0; n < pixelData.NumberOfFrames; n++) { @@ -82,13 +101,11 @@ namespace IRaCIS.Core.Application.Service newFragments.Fragments.Add(new MemoryByteBuffer(frameData.Data)); } - var frag = dicomFile.Dataset.GetDicomItem(DicomTag.PixelData); - - var originOffsetTable = frag?.OffsetTable; - - newFragments.OffsetTable.AddRange(originOffsetTable?.ToArray()); // 替换原有的片段序列 dicomFile.Dataset.AddOrUpdate(newFragments); + + #endregion + } await dicomFile.SaveAsync(output); diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index c38ebff88..70458f08a 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -104,11 +104,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc newFragments.Fragments.Add(new MemoryByteBuffer(frameData.Data)); } - var frag = dicomFile.Dataset.GetDicomItem(DicomTag.PixelData); + //var frag = dicomFile.Dataset.GetDicomItem(DicomTag.PixelData); - var originOffsetTable = frag?.OffsetTable; + //var originOffsetTable = frag?.OffsetTable; - newFragments.OffsetTable.AddRange(originOffsetTable?.ToArray()); + //newFragments.OffsetTable.AddRange(originOffsetTable?.ToArray()); // 替换原有的片段序列 dicomFile.Dataset.AddOrUpdate(newFragments); } From c9ffa64560163392d6e051ea003a2e6717423f76 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 13:54:10 +0800 Subject: [PATCH 19/25] =?UTF-8?q?=E9=81=AE=E7=9B=96=E5=BD=B1=E5=83=8F?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 24 +++++++++++++--- .../Service/ImageAndDoc/StudyService.cs | 28 ++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 6ba580e20..ac35dd052 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -60,6 +60,14 @@ namespace IRaCIS.Core.Application.Service { try { + + #region 方式一 有的必须在内存中,不能用这种 + //await using var source = await sourceFactory(); + //// 如果你是从 stream 打开 + //var dicomFile = await DicomFile.OpenAsync(source); + #endregion + + #region 方式二 await using var source = await sourceFactory(); // 【关键修复】将 OSS 流缓冲到 MemoryStream @@ -79,6 +87,8 @@ namespace IRaCIS.Core.Application.Service // 如果你是从 stream 打开 var dicomFile = await DicomFile.OpenAsync(source.CanSeek ? source : bufferedStream); + #endregion + //获取像素是否为封装形式 var syntax = dicomFile.Dataset.InternalTransferSyntax; @@ -211,8 +221,12 @@ namespace IRaCIS.Core.Application.Service #endregion + var oldVisits = MiniExcel.Query(Path.Combine(rootFolder, "Old.xlsx")).ToList(); - var downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()); + var downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()).ToList(); + + downloadVisits= downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && + old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); var downloadJobs = new List(); @@ -308,6 +322,7 @@ namespace IRaCIS.Core.Application.Service continue; } + Log.Logger.Warning($"开始获取访视信息准备下载任务:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); foreach (var studyInfo in visitItem.StudyList) { @@ -502,6 +517,7 @@ namespace IRaCIS.Core.Application.Service return Task.CompletedTask; } }); + } @@ -515,14 +531,14 @@ namespace IRaCIS.Core.Application.Service - - + Log.Logger.Warning($"访视信息准备完毕: {downloadVisits.Count},后端开始下载任务......"); + #region 异步方式处理 int totalCount = downloadJobs.Count; int downloadedCount = 0; - Log.Logger.Warning($"开始下载总数: {totalCount}"); + Log.Logger.Warning($"下载文件总数: {totalCount}"); foreach (var job in downloadJobs) { try diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index 70458f08a..cb899be04 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -82,9 +82,35 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc { try { + #region 方式一 有的必须在内存中,不能用这种 + //await using var source = await sourceFactory(); + //// 如果你是从 stream 打开 + //var dicomFile = await DicomFile.OpenAsync(source); + #endregion + + #region 方式二 + await using var source = await sourceFactory(); + + // 【关键修复】将 OSS 流缓冲到 MemoryStream + using var bufferedStream = new MemoryStream(); + + if (source.CanSeek) + { + source.Position = 0; + + } + else + { + // 完全复制到内存流 + await source.CopyToAsync(bufferedStream); + bufferedStream.Position = 0; // 重置位置 + } + // 如果你是从 stream 打开 - var dicomFile = await DicomFile.OpenAsync(source); + var dicomFile = await DicomFile.OpenAsync(source.CanSeek ? source : bufferedStream); + + #endregion //获取像素是否为封装形式 var syntax = dicomFile.Dataset.InternalTransferSyntax; From ff2c619df7e51209ff9eff909558a8980251a5f4 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 14:09:12 +0800 Subject: [PATCH 20/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E9=A2=9D=E5=A4=96=E6=97=A5=E5=BF=97=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Common/TrialImageDownloadService.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index ac35dd052..2323b74b4 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -230,6 +230,7 @@ namespace IRaCIS.Core.Application.Service var downloadJobs = new List(); + var skipCount = 0; foreach (var downloadVisit in downloadVisits) { var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new @@ -305,6 +306,8 @@ namespace IRaCIS.Core.Application.Service if (existVisits.Any(old => old.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) { + Log.Logger.Warning($"Excel显示已下载,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + skipCount++; continue; } @@ -319,6 +322,8 @@ namespace IRaCIS.Core.Application.Service { if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) { + Log.Logger.Warning($"查询无检查,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + skipCount++; continue; } @@ -480,7 +485,7 @@ namespace IRaCIS.Core.Application.Service Action = async () => { - Log.Logger.Warning($" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); + Log.Logger.Warning($" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}开启另外线程压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); string zipPath = visitFolderPath + ".zip"; @@ -531,7 +536,7 @@ namespace IRaCIS.Core.Application.Service - Log.Logger.Warning($"访视信息准备完毕: {downloadVisits.Count},后端开始下载任务......"); + Log.Logger.Warning($"{downloadVisits.Count}个访视信息核对准备完毕, 跳过{skipCount} 个,后端开始下载任务......"); #region 异步方式处理 From 8b046e61ab4738041e14498163c04662d74f6a72 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 14:34:25 +0800 Subject: [PATCH 21/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E9=95=BF=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E4=B8=8B=E8=BD=BD=EF=BC=8C=E4=B8=B4=E6=97=B6token=20?= =?UTF-8?q?=E7=BB=AD=E6=9C=9Fbug=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.Application/Helper/OSSService.cs | 46 +++++++++++++------ .../Common/TrialImageDownloadService.cs | 14 +++--- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index ec04c3f08..1114481db 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -220,7 +220,7 @@ public class OSSService(IOptionsMonitor options, public object result { get; private set; } - + private static readonly object _tokenLock = new(); /// /// 将指定前缀下的所有现有文件立即转为目标存储类型 @@ -934,32 +934,52 @@ public class OSSService(IOptionsMonitor options, //后端批量上传 或者下载,不每个文件获取临时token private void BackBatchGetToken() { + + + + if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS") { - if (AliyunOSSTempToken == null) + if (AliyunOSSTempToken != null && AliyunOSSTempToken.Expiration > DateTime.UtcNow.AddMinutes(15)) { - GetObjectStoreTempToken(); - } - //token 过期了 - if (AliyunOSSTempToken?.Expiration.AddSeconds(10) <= DateTime.Now) - { - GetObjectStoreTempToken(); + return; } + lock (_tokenLock) + { + if (AliyunOSSTempToken == null || + AliyunOSSTempToken.Expiration <= DateTime.UtcNow.AddMinutes(15)) + { + GetObjectStoreTempToken(); + + Log.Logger.Warning("后端获取阿里云临时 Token"); + } + } } else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS") { - if (AWSTempToken == null) + + + if (AWSTempToken != null && AWSTempToken.Expiration > DateTime.UtcNow.AddMinutes(15)) { - GetObjectStoreTempToken(); + return; } - //token 过期了 - if (AWSTempToken.Expiration?.AddSeconds(10) <= DateTime.Now) + + lock (_tokenLock) { - GetObjectStoreTempToken(); + if (AWSTempToken == null || + AWSTempToken.Expiration <= DateTime.UtcNow.AddMinutes(15)) + { + GetObjectStoreTempToken(); + + Log.Logger.Warning("后端获取s3 临时 Token"); + } } + } + + } diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 2323b74b4..971eef75e 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -225,14 +225,16 @@ namespace IRaCIS.Core.Application.Service var downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()).ToList(); - downloadVisits= downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && + downloadVisits = downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); var downloadJobs = new List(); var skipCount = 0; + var visitIndex = 0; foreach (var downloadVisit in downloadVisits) { + visitIndex++; var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, @@ -306,7 +308,7 @@ namespace IRaCIS.Core.Application.Service if (existVisits.Any(old => old.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) { - Log.Logger.Warning($"Excel显示已下载,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + Log.Logger.Warning($"[{visitIndex}] Excel显示已下载,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); skipCount++; continue; } @@ -322,12 +324,12 @@ namespace IRaCIS.Core.Application.Service { if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) { - Log.Logger.Warning($"查询无检查,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + Log.Logger.Warning($"[{visitIndex}]查询无检查,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); skipCount++; continue; } - Log.Logger.Warning($"开始获取访视信息准备下载任务:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); + Log.Logger.Warning($"[{visitIndex}]开始获取访视信息准备下载任务:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); foreach (var studyInfo in visitItem.StudyList) { @@ -485,7 +487,7 @@ namespace IRaCIS.Core.Application.Service Action = async () => { - Log.Logger.Warning($" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}开启另外线程压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); + Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}开启另外线程压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); string zipPath = visitFolderPath + ".zip"; @@ -537,7 +539,7 @@ namespace IRaCIS.Core.Application.Service Log.Logger.Warning($"{downloadVisits.Count}个访视信息核对准备完毕, 跳过{skipCount} 个,后端开始下载任务......"); - + #region 异步方式处理 int totalCount = downloadJobs.Count; From 99b6c63de76ddf5949612974653ddd57cee344a0 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 15:33:03 +0800 Subject: [PATCH 22/25] =?UTF-8?q?=E5=BC=80=E5=90=AF=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E5=8C=85=E4=BB=BB=E5=8A=A1=E7=B4=A2=E5=BC=95=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Common/TrialImageDownloadService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 971eef75e..cc1d910ce 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -479,6 +479,8 @@ namespace IRaCIS.Core.Application.Service //建立压缩包 string visitFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}"); + var currentIndex = visitIndex; + downloadJobs.Add(new DownloadJob() { Name = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}_Zip", @@ -487,7 +489,7 @@ namespace IRaCIS.Core.Application.Service Action = async () => { - Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}开启另外线程压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); + Log.Logger.Warning($"[{currentIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}开启另外线程压缩访视:{visitItem.SubjectCode} {visitItem.VisitName} {visitItem.VisitNum}"); string zipPath = visitFolderPath + ".zip"; From bb3f61277213001882c908213364fc038b6a0b35 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 17:44:31 +0800 Subject: [PATCH 23/25] =?UTF-8?q?=E4=B8=B4=E6=97=B6token=20=E7=BB=AD?= =?UTF-8?q?=E6=9C=9F=E4=BB=A5=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=AF=94=E8=BE=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Helper/DicomDIRHelper.cs | 16 +- IRaCIS.Core.Application/Helper/OSSService.cs | 6 +- .../Common/TrialImageDownloadService.cs | 355 ++++++++++++++++++ 3 files changed, 371 insertions(+), 6 deletions(-) diff --git a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs index dd3b7c140..f41bd13cd 100644 --- a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs +++ b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs @@ -3,6 +3,7 @@ using FellowOakDicom; using FellowOakDicom.Media; using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Domain.Models; +using NPOI.Util; using System; using System.Collections.Generic; using System.Data; @@ -160,7 +161,7 @@ namespace IRaCIS.Core.Application.Helper } - public static async Task GenerateStudyDIR(List list, Dictionary dic,string dirSavePath) + public static async Task GenerateStudyDIR(List list, Dictionary dic, string? dirSavePath = null, Stream? outputStream = null) { var mappings = new List(); int index = 1; @@ -228,9 +229,18 @@ namespace IRaCIS.Core.Application.Helper //有实际的文件 if (mappings.Count > 0) - { + { // 保存 DICOMDIR 到临时文件 不能直接写入到流种 - await dicomDir.SaveAsync(dirSavePath); + + if (dirSavePath.IsNotNullOrEmpty()) + { + await dicomDir.SaveAsync(dirSavePath); + + } + else + { + await dicomDir.SaveAsync(outputStream); + } } } diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 1114481db..57c28727c 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -948,7 +948,7 @@ public class OSSService(IOptionsMonitor options, lock (_tokenLock) { if (AliyunOSSTempToken == null || - AliyunOSSTempToken.Expiration <= DateTime.UtcNow.AddMinutes(15)) + AliyunOSSTempToken.Expiration <= DateTime.Now.AddMinutes(15)) { GetObjectStoreTempToken(); @@ -961,7 +961,7 @@ public class OSSService(IOptionsMonitor options, { - if (AWSTempToken != null && AWSTempToken.Expiration > DateTime.UtcNow.AddMinutes(15)) + if (AWSTempToken != null && AWSTempToken.Expiration > DateTime.Now.AddMinutes(15)) { return; } @@ -969,7 +969,7 @@ public class OSSService(IOptionsMonitor options, lock (_tokenLock) { if (AWSTempToken == null || - AWSTempToken.Expiration <= DateTime.UtcNow.AddMinutes(15)) + AWSTempToken.Expiration <= DateTime.Now.AddMinutes(15)) { GetObjectStoreTempToken(); diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index cc1d910ce..bba480b31 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; +using NPOI.Util; using Org.BouncyCastle.Utilities.Zlib; using SharpCompress.Common; using System; @@ -130,6 +131,358 @@ namespace IRaCIS.Core.Application.Service } + #region zip 流方式 直接下载 + + + public sealed class ZipItem + { + public string ZipEntryPath { get; set; } = ""; + + public string? OssPath { get; set; } + + public bool IsEncapsulated { get; set; } + + public Func? CustomWriter { get; set; } + } + + + private async Task CreateVisitZipAsync(string zipPath, List zipItems) + { + Directory.CreateDirectory( + Path.GetDirectoryName(zipPath)!); + + await using var zipFileStream = + new FileStream(zipPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4 * 1024 * 1024, useAsync: true); + + using var archive = new ZipArchive(zipFileStream, ZipArchiveMode.Create, leaveOpen: false); + + foreach (var item in zipItems) + { + var entry = archive.CreateEntry(item.ZipEntryPath.Replace("\\", "/"), CompressionLevel.NoCompression); + + await using var entryStream = entry.Open(); + + if (item.CustomWriter != null) + { + await item.CustomWriter(entryStream); + continue; + } + + if (string.IsNullOrWhiteSpace(item.OssPath)) + continue; + + if (item.IsEncapsulated) + { + var success = await TryWriteMergedDicomAsync(() => _oSSService.GetStreamFromOSSAsync(item.OssPath), entryStream); + + if (!success) + { + Log.Logger.Warning($"合并多帧失败:{item.ZipEntryPath}"); + } + + } + else + { + await using var ossStream = await _oSSService.GetStreamFromOSSAsync(item.OssPath); + + await ossStream.CopyToAsync(entryStream, 4 * 1024 * 1024); + } + + + } + } + + //查询一个访视 + // ↓ + //生成 DICOMDIR + // ↓ + //创建 ZipArchive + // ↓ + //OSS流直接写 ZipEntry + // ↓ + //完成一个访视.zip + // ↓ + //记录日志 + // ↓ + //处理下一个访视 + + [HttpPost] + [AllowAnonymous] + public async Task DownloadExcelTrialImageZIPStream(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 downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()).ToList(); + + downloadVisits = downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && + old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + + var visitIndex = 0; + var skipCount = 0; + foreach (var downloadVisit in downloadVisits) + { + var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new + { + t.ResearchProgramNo, + t.TrialCode, + + VisitList = t.SubjectVisitList.Where(t => t.VisitName.Trim() == downloadVisit.VisitName.Trim() && t.Subject.Code.Trim() == downloadVisit.SubjectCode.Trim() && t.VisitNum == downloadVisit.VisitNum) + .Select(sv => new + { + SubjectVisitId = sv.Id, + TrialSiteCode = sv.TrialSite.TrialSiteCode, + SubjectCode = sv.Subject.Code, + VisitName = sv.VisitName, + VisitNum = sv.VisitNum, + StudyList = sv.StudyList.Select(u => new + { + StudyId = u.Id, + u.PatientId, + u.StudyTime, + u.StudyCode, + u.StudyInstanceUid, + u.StudyDIRPath, + + SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new + { + z.Modality, + + InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new + { + InstanceId = k.Id, + k.Path, + k.IsEncapsulated, + k.NumberOfFrames, + }).ToList() + }) + + }).ToList(), + + NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new + { + nd.Modality, + nd.StudyCode, + nd.ImageDate, + + FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new + { + file.FileName, + file.Path, + file.FileType + }).ToList() + }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() + + }).FirstOrDefault(); + + if (downloadInfo == null) + { + return ResponseOutput.Ok(); + } + + #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.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && + old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) + { + Log.Logger.Warning($"[{visitIndex}] Excel显示已下载,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + skipCount++; + continue; + } + + } + + + #endregion + + foreach (var visitItem in downloadInfo.VisitList) + { + if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) + { + Log.Logger.Warning($"[{visitIndex}]查询无检查,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + skipCount++; + continue; + } + + try + { + #region 导出访视 + + var zipItems = new List(); + + var visitFolderName = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}"; + + foreach (var studyInfo in visitItem.StudyList) + { + var dirDic = new Dictionary(); + + var studyFolderName = $"{studyInfo.StudyCode}_{studyInfo.StudyTime:yyyy-MM-dd}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality))}"; + + #region DICOMDIR + + + if (!_instanceRepository.Where(t => t.IsReading && t.DicomSerie.IsReading) + .Where(t => visitItem.SubjectVisitId == t.SubjectVisitId).Any(c => c.TransferSytaxUID == string.Empty)) + { + var list = _subjectVisitRepository.Where(t => t.Id == visitItem.SubjectVisitId).SelectMany(t => t.StudyList) + .SelectMany(t => t.InstanceList.Where(t => t.IsReading && t.DicomSerie.IsReading && t.StudyId == studyInfo.StudyId)) + .Select(t => new StudyDIRInfo() + { + + DicomStudyId = t.DicomStudy.Id, + + PatientId = downloadInfo.TrialCode + "-" + t.DicomStudy.Subject.Code, + PatientName = t.DicomStudy.PatientName, + PatientBirthDate = t.DicomStudy.PatientBirthDate, + PatientSex = t.DicomStudy.PatientSex, + + StudyInstanceUid = t.StudyInstanceUid, + StudyId = t.DicomStudy.StudyId, + DicomStudyDate = t.DicomStudy.DicomStudyDate, + DicomStudyTime = t.DicomStudy.DicomStudyTime, + AccessionNumber = t.DicomStudy.AccessionNumber, + + StudyDescription = t.DicomStudy.Description, + + SeriesInstanceUid = t.DicomSerie.SeriesInstanceUid, + Modality = t.DicomSerie.Modality, + DicomSeriesDate = t.DicomSerie.DicomSeriesDate, + DicomSeriesTime = t.DicomSerie.DicomSeriesTime, + SeriesNumber = t.DicomSerie.SeriesNumber, + SeriesDescription = t.DicomSerie.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 })) + { + + + zipItems.Add(new ZipItem + { + ZipEntryPath = $"{visitFolderName}/{studyFolderName}/DICOMDIR", + + CustomWriter = async entryStream => + { + await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, outputStream: entryStream); + } + }); + } + + } + + + #endregion + + + #region DICOM + + 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 visitItem.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 + + var zipPath = Path.Combine(rootFolder, visitFolderName + ".zip"); + + Log.Logger.Warning($"开始压缩访视:{visitFolderName}"); + + await CreateVisitZipAsync(zipPath, zipItems); + + DownloadLogger.Write(logFilePath, visitItem.SubjectCode, visitItem.VisitNum, visitItem.VisitName, "Success"); + + Log.Logger.Warning($"访视压缩完成:{visitFolderName}"); + + #endregion + + } + + catch (Exception ex) + { + Log.Logger.Error(ex, $"导出失败:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); + } + + + } + } + + return ResponseOutput.Ok(); + } + + #endregion + + + #region Excel 下载 先下载,然后再压缩,删除方式 public static class DownloadLogger { @@ -1000,6 +1353,8 @@ namespace IRaCIS.Core.Application.Service } + #endregion + /// /// 下载影像 维护dir信息 并回传到OSS From 73fa62e6a2ac0785c1afb539844e5fc4fec76244 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 3 Jun 2026 17:56:07 +0800 Subject: [PATCH 24/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BB=AD=E6=9C=9F?= =?UTF-8?q?=E5=A4=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.Application/Helper/OSSService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 57c28727c..93f96b3f2 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -936,13 +936,13 @@ public class OSSService(IOptionsMonitor options, { - + // 过期时间 ≤ 当前时间 + 15分钟 时需要续期 if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS") { - if (AliyunOSSTempToken != null && AliyunOSSTempToken.Expiration > DateTime.UtcNow.AddMinutes(15)) + if (AliyunOSSTempToken != null && AliyunOSSTempToken.Expiration > DateTime.Now.AddMinutes(15)) { - return; + return; // 还有15分钟以上,不需要续期 } lock (_tokenLock) From d719015fa2e28a6792192697eac7a5472aee333c Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 4 Jun 2026 13:08:37 +0800 Subject: [PATCH 25/25] =?UTF-8?q?zip=20=E6=B5=81=E6=96=B9=E5=BC=8Fdemo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 88 +++++++++++++------ IRaCIS.Core.Application/TestService.cs | 23 ----- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index bba480b31..5bf9183b6 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -1,4 +1,5 @@ using Aliyun.OSS; +using CommunityToolkit.HighPerformance; using DocumentFormat.OpenXml.EMMA; using DocumentFormat.OpenXml.Office.CustomUI; using DocumentFormat.OpenXml.Office2010.Excel; @@ -148,11 +149,9 @@ namespace IRaCIS.Core.Application.Service private async Task CreateVisitZipAsync(string zipPath, List zipItems) { - Directory.CreateDirectory( - Path.GetDirectoryName(zipPath)!); + Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!); - await using var zipFileStream = - new FileStream(zipPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4 * 1024 * 1024, useAsync: true); + await using var zipFileStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4 * 1024 * 1024, useAsync: true); using var archive = new ZipArchive(zipFileStream, ZipArchiveMode.Create, leaveOpen: false); @@ -177,7 +176,9 @@ namespace IRaCIS.Core.Application.Service if (!success) { - Log.Logger.Warning($"合并多帧失败:{item.ZipEntryPath}"); + Log.Logger.Warning($"合并多帧失败:{item.ZipEntryPath} ossPath: {item.OssPath}"); + + throw new Exception($"合并多帧失败-终止当前zip包:{item.ZipEntryPath} ossPath: {item.OssPath}"); } } @@ -235,8 +236,7 @@ namespace IRaCIS.Core.Application.Service var downloadVisits = MiniExcel.Query(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.VisitName.IsNotNullOrEmpty()).ToList(); - downloadVisits = downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && - old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); + downloadVisits = downloadVisits.Where(t => !oldVisits.Any(old => old.VisitNum == t.VisitNum && old.SubjectCode == t.SubjectCode && old.VisitName.Trim().ToLower() == t.VisitName.Trim().ToLower())).ToList(); var visitIndex = 0; var skipCount = 0; @@ -298,7 +298,7 @@ namespace IRaCIS.Core.Application.Service if (downloadInfo == null) { - return ResponseOutput.Ok(); + continue; } #region 排除已经下载的 @@ -312,8 +312,7 @@ namespace IRaCIS.Core.Application.Service StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312")) }).ToList(); - if (existVisits.Any(old => old.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && - old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) + if (existVisits.Any(old => old.VisitNum == downloadVisit.VisitNum && old.SubjectCode == downloadVisit.SubjectCode && old.VisitName.Trim().ToLower() == downloadVisit.VisitName.Trim().ToLower())) { Log.Logger.Warning($"[{visitIndex}] Excel显示已下载,跳过当前访视:{downloadVisit.SubjectCode} {downloadVisit.VisitName} {downloadVisit.VisitNum}"); skipCount++; @@ -342,6 +341,8 @@ namespace IRaCIS.Core.Application.Service var visitFolderName = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}"; + #region DICOM + foreach (var studyInfo in visitItem.StudyList) { var dirDic = new Dictionary(); @@ -394,6 +395,11 @@ namespace IRaCIS.Core.Application.Service 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 { @@ -401,7 +407,7 @@ namespace IRaCIS.Core.Application.Service CustomWriter = async entryStream => { - await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, outputStream: entryStream); + await entryStream.WriteAsync(dicomDirBytes); } }); } @@ -412,7 +418,7 @@ namespace IRaCIS.Core.Application.Service #endregion - #region DICOM + foreach (var seriesInfo in studyInfo.SeriesList) { @@ -429,11 +435,13 @@ namespace IRaCIS.Core.Application.Service } } - #endregion } + #endregion + + #region NoneDicom foreach (var study in visitItem.NoneDicomStudyList) @@ -453,15 +461,40 @@ namespace IRaCIS.Core.Application.Service #endregion - var zipPath = Path.Combine(rootFolder, visitFolderName + ".zip"); - Log.Logger.Warning($"开始压缩访视:{visitFolderName}"); + #region zip - await CreateVisitZipAsync(zipPath, zipItems); - DownloadLogger.Write(logFilePath, visitItem.SubjectCode, visitItem.VisitNum, visitItem.VisitName, "Success"); + 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.Write(logFilePath, visitItem.SubjectCode, visitItem.VisitNum, visitItem.VisitName, "Success"); + + } + catch (Exception ex) + { + Log.Logger.Warning($"出现异常{ex}删除压缩包:{visitFolderName}"); + //如果有异常,删除失败的压缩包 + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + } + + + #endregion + - Log.Logger.Warning($"访视压缩完成:{visitFolderName}"); #endregion @@ -486,8 +519,6 @@ namespace IRaCIS.Core.Application.Service public static class DownloadLogger { - - public static void Write( string logFilePath, string subjectCode, @@ -495,27 +526,34 @@ namespace IRaCIS.Core.Application.Service string visitName, string? message = null) { - bool fileExists = File.Exists(logFilePath); + // 一次性打开文件流 using var stream = new FileStream( logFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); - using var writer = new StreamWriter(stream, Encoding.UTF8); + // 首次创建时写入 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,VisitNum,VisitName,Message"); } + // 写入数据 string line = string.Join(",", [ - Escape(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), + Escape(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), subjectCode, visitNum, visitName, @@ -525,14 +563,12 @@ namespace IRaCIS.Core.Application.Service writer.WriteLine(line); } - // 防止逗号、换行导致 CSV 错乱 private static string Escape(string? value) { if (string.IsNullOrEmpty(value)) return ""; value = value.Replace("\"", "\"\""); - return $"\"{value}\""; } } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 34e6f3364..83ca9053e 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -1,30 +1,17 @@ using Aliyun.OSS; -using DocumentFormat.OpenXml.Spreadsheet; using FellowOakDicom; using FellowOakDicom.Imaging; using IRaCIS.Application.Contracts; -using IRaCIS.Core.Application.BusinessFilter; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Helper; -using IRaCIS.Core.Application.Helper.OtherTool; -using IRaCIS.Core.Application.Service.BusinessFilter; using IRaCIS.Core.Application.ViewModel; -using IRaCIS.Core.Domain; -using IRaCIS.Core.Domain.Models; -using IRaCIS.Core.Domain.Share; -using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore.Context; using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure.Encryption; using IRaCIS.Core.Infrastructure.NewtonsoftJson; using MassTransit; -using MassTransit.Caching.Internals; -using MassTransit.Mediator; -using MathNet.Numerics; -using MaxMind.GeoIP2; using Medallion.Threading; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -32,25 +19,15 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MiniExcelLibs; -using Minio.DataModel; using Newtonsoft.Json; -using NPOI.SS.Formula.Functions; using NPOI.XWPF.UserModel; -using SharpCompress.Common; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; -using System.Collections.Concurrent; using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.Globalization; -using System.IO; using System.Linq.Dynamic.Core; -using System.Reactive.Subjects; -using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Text; -using static IRaCIS.Core.Domain.Share.StaticData;