下载IR 上传影像

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