From 14e212c4a838ab9c0f68b981fb9b441319e3712d Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Thu, 16 Oct 2025 13:33:42 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A6=86=E7=9B=96TrialImageDownloadService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 1178 ----------------- 1 file changed, 1178 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index 462954b67..f43b076b9 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -1419,1184 +1419,6 @@ namespace IRaCIS.Core.Application.Service - public async Task OSSDeleteReStorre([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment) - { - var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS; - - var tempToken = _oSSService.GetObjectStoreTempToken(); - - var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, - tempToken.AliyunOSS.AccessKeyId, - tempToken.AliyunOSS.AccessKeySecret, - tempToken.AliyunOSS.SecurityToken); - - - var allVersions = new List(); - var allDeleteMarkers = new List(); - - var allDeleteDistinceKeys = new List(); - - var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName) - { - Prefix = "test-delete-restore", - //Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3", - MaxKeys = 1000, - }; - - ObjectVersionList result; - do - { - - result = _ossClient.ListObjectVersions(request); - - if (result.ObjectVersionSummaries != null) - allVersions.AddRange(result.ObjectVersionSummaries); - - if (result.DeleteMarkerSummaries != null) - allDeleteMarkers.AddRange(result.DeleteMarkerSummaries); - - request.KeyMarker = result.NextKeyMarker; - request.VersionIdMarker = result.NextVersionIdMarker; - - } while (result.IsTruncated); - - Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记"); - - var versionsByKey = allVersions - .GroupBy(v => v.Key) - .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList()); - - - foreach (var del in allDeleteMarkers) - { - #region 防止阿里云过期 - if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now) - { - tempToken = _oSSService.GetObjectStoreTempToken(); - - _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, - tempToken.AliyunOSS.AccessKeyId, - tempToken.AliyunOSS.AccessKeySecret, - tempToken.AliyunOSS.SecurityToken); - } - - #endregion - - if (!versionsByKey.TryGetValue(del.Key, out var versions)) - continue; // 没有历史版本无法恢复 - - var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified); - if (prevVersion == null) - continue; // 没找到可恢复版本 - - //这里会出现同一个key 删除多次,然后下载多次,恢复的时候插入数据有问题 - - if (allDeleteDistinceKeys.Contains(del.Key)) - { - continue; - } - - allDeleteDistinceKeys.Add(del.Key); - - - // 创建 CopyObject 请求 - // 先用构造函数指定源和目标 - var copyReq = new CopyObjectRequest( - sourceBucketName: tempToken.AliyunOSS.BucketName, - sourceKey: prevVersion.Key, - destinationBucketName: tempToken.AliyunOSS.BucketName, - destinationKey: prevVersion.Key // 覆盖到同名 Key,达到“恢复”的效果 - ); - - // 再设置版本号 - copyReq.SourceVersionId = prevVersion.VersionId; - - - - try - { - var copyResult = _ossClient.CopyObject(copyReq); - Console.WriteLine($"✅ 恢复成功: {prevVersion.Key}, 新版本ID={copyResult.VersionId}"); - } - catch (Exception ex) - { - Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}"); - } - - - } - - return ResponseOutput.Ok(); - } - - - /// - /// 维护dir 需求新增的字段 - - /// - /// - /// - /// - /// - /// - public async Task TrialImageAddExtralField(Guid trialId, - [FromServices] IRepository _instanceRepository, - [FromServices] IRepository _studyRepository, - [FromServices] IRepository _seriesRepository) - { - // UPDATE DicomStudy - //SET DicomStudyDate = CONVERT(char(8), StudyTime, 112), --yyyyMMdd - // DicomStudyTime = REPLACE(CONVERT(char(8), StudyTime, 108), ':', ''); --HHmmss - // where DicomStudyDate = '' - - - //instance 找到传输语法为空的,然后分组 - var seriesList = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "") - //按照序列 和 NumberOfFrames 分组 - .GroupBy(t => new { t.NumberOfFrames, t.SeriesId }) - // 每个分组 取数据最小的一条 - .Select(g => new { g.Key.SeriesId, g.Key.NumberOfFrames, g.OrderBy(t => t.FileSize).First().Path }).ToList(); - - foreach (var item in seriesList) - { - var stream = await _oSSService.GetStreamFromOSSAsync(item.Path); - - var dicomFile = DicomFile.Open(stream); - - var pixelData = DicomPixelData.Create(dicomFile.Dataset); - - //获取像素是否为封装形式 - var syntax = dicomFile.Dataset.InternalTransferSyntax; - - //读取需要维护的值 - var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty); - var mediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty); - var mediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty); - var sOPClassUID = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty); - - //维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID - await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.SeriesId == item.SeriesId, t => new DicomInstance() - { - IsEncapsulated = syntax.IsEncapsulated, - TransferSytaxUID = transferSyntaxUID, - MediaStorageSOPClassUID = mediaStorageSOPClassUID, - SOPClassUID = sOPClassUID, - }); - - } - - return ResponseOutput.Ok(); - - } - - - /// - /// 下载已经删除的影像 - /// - /// - /// - [HttpPost] - [AllowAnonymous] - public async Task DownloadDeleteTrialImage(Guid trialId) - { - trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091"); - - var downloadInfo = _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => new - { - t.ResearchProgramNo, - - VisitList = t.SubjectVisitList - .Select(sv => new - { - TrialSiteCode = sv.TrialSite.TrialSiteCode, - SubjectCode = sv.Subject.Code, - VisitName = sv.VisitName, - StudyList = sv.StudyList.Select(u => new - { - u.PatientId, - u.StudyTime, - u.StudyCode, - - SeriesList = u.SeriesList.Select(z => new - { - z.Modality, - z.SeriesNumber, - - InstancePathList = z.DicomInstanceList.Where(t => t.IsDeleted == true || t.DicomSerie.IsDeleted == true || t.IsReading == false || t.DicomSerie.IsReading == false).Select(k => new - { - k.Path, - k.FileSize - }).ToList() - }) - - }).ToList() - }).ToList() - - }).FirstOrDefault(); - - - var filesizes = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Sum(t => t.FileSize); - var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count(); - - Console.WriteLine($"下载总数量:{count2},总大小{filesizes}"); - - if (downloadInfo != null) - { - var downloadJobs = new List<(string Path, Func Job)>(); - - 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) - { - 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) - { - string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}", $"{seriesInfo.SeriesNumber}"); - - // 创建 影像 文件夹 - Directory.CreateDirectory(studyDicomFolderPath); - - // 遍历 InstancePathList - foreach (var instanceInfo in seriesInfo.InstancePathList) - { - // 复制文件到相应的文件夹 - string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path)); - - - //加入到下载任务里 - downloadJobs.Add((instanceInfo.Path, () => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath))); - - //下载到当前目录 - //await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath); - } - } - - - } - } - - #region 异步方式处理 - - int totalCount = downloadJobs.Count; - int downloadedCount = 0; - - // 在 trialFolderPath 下面放一个失败记录文件 - string failedLogPath = Path.Combine(trialFolderPath, "failed_downloads.txt"); - - // 确保文件存在(如果之前有就清空) - File.WriteAllText(failedLogPath, ""); - - foreach (var job in downloadJobs) - { - try - { - await job.Job(); - } - catch (Exception ex) - { - - - string errorMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 下载失败: {job.Path}, 错误: {ex.Message}\r\n"; - - Console.WriteLine(errorMessage); - - await File.AppendAllTextAsync(failedLogPath, errorMessage); - } - - downloadedCount++; - - // 每处理50个,输出一次进度(或最后一个时也输出) - if (downloadedCount % 50 == 0 || downloadedCount == totalCount) - { - Console.WriteLine($"已下载 {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(); - - - } - - - [AllowAnonymous] - public async Task ReadDicomDataWriteDB([FromServices] IRepository _instanceRepository) - { - var testPath = @"E:\WXT001"; - var path = @"E:\WXT001"; - - var files = Directory.GetFiles(testPath, "*", SearchOption.AllDirectories) - // 只要没有后缀(Windows 显示类型是 .file) - .Where(f => string.IsNullOrEmpty(Path.GetExtension(f))) - .Where(f => Guid.TryParse(Path.GetFileNameWithoutExtension(f), out _)) - .ToList(); - - Console.WriteLine($"找到 {files.Count} 个 DICOM 文件"); - - int total = files.Count; - int processed = 0; - double lastPercent = 0; - - var options = new ParallelOptions { MaxDegreeOfParallelism = 12 }; - - // 输出文件路径 - var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt"); - - var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt"); - - // 用并发安全的写法(锁保护) - var fileLock = new object(); - - foreach (var file in files) - { - try - { - var id = Guid.Parse(Path.GetFileNameWithoutExtension(file)); - var dicomFile = DicomFile.Open(file); - - var dataset = dicomFile.Dataset; - var fileMeta = dicomFile.FileMetaInfo; - var syntax = dataset.InternalTransferSyntax; - - - - //单位 设备 PatientId Visit 检查UId 帧数 - - var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty); - - var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty); - - var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty); - - //PatientID TrialCode_SubjectCode - //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty); - - //SubjectCode - var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty); - //访视visitNum - var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty); - var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty); - var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty); - var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty); - - var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1); - - - // 传输语法 - var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty); - var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty); - var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty); - - var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty); - - - // 拼接一行 CSV 格式 - var line = string.Join(",", - id, - stationName, - institutionName, - manufacturer, - clinicalTrialSubjectID, - clinicalTrialTimePointID, - studyInstanceUID, - seriesInstanceUID, - sOPInstanceUID, - numberOfFrames, - transferSyntaxUID, - sOPClassUID, - mediaStorageSOPClassUID, - mediaStorageSOPInstanceUID - - ); - - - await File.AppendAllTextAsync(outputFile, line + Environment.NewLine); - - await _instanceRepository.BatchUpdateNoTrackingAsync( - t => t.Id == id, - t => new DicomInstance - { - IsEncapsulated = syntax.IsEncapsulated, - TransferSytaxUID = transferSyntaxUID, - MediaStorageSOPClassUID = mediaStorageSOPClassUID, - SOPClassUID = sOPClassUID, - MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID - }, false); - } - catch (Exception ex) - { - var errorMsg = $"{DateTime.Now}❌ {file} 解析失败: {ex.Message}"; - Console.WriteLine(errorMsg); - - await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine); - } - finally - { - var done = Interlocked.Increment(ref processed); - double percent = done * 100.0 / total; - - // 只在进度提升 >= 1% 时打印 - if (percent - lastPercent >= 5.0 || done == total) - { - lastPercent = percent; - Console.WriteLine($"{DateTime.Now} 进度: {done}/{total} ({percent:F2}%)"); - } - } - - } - - - - - - - return ResponseOutput.Ok(); - } - - #region 维护已经下载本地的数据 - - [AllowAnonymous] - public async Task ReadExcelData([FromServices] IRepository _instanceRepository) - { - var rows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\维护数据读取.xlsx"); - - rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - int total = rows.Count(); - int processed = 0; - double lastPercent = 0; - - var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt"); - - foreach (var item in rows) - { - - - try - { - await _instanceRepository.BatchUpdateNoTrackingAsync( - t => t.Id == item.InstanceId, - t => new DicomInstance - { - - IsEncapsulated = item.IsEncapsulated, - TransferSytaxUID = item.TransferSyntaxUID, - MediaStorageSOPClassUID = item.MediaStorageSOPClassUID, - SOPClassUID = item.SOPClassUID, - MediaStorageSOPInstanceUID = item.MediaStorageSOPInstanceUID - }, false); - } - catch (Exception ex) - { - - var errorMsg = $"{item.InstanceId} {DateTime.Now} 更新失败: {ex.Message}"; - Console.WriteLine(errorMsg); - - await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine); - } - finally - { - processed++; - double percent = processed * 100.0 / total; - - // 每提升 5% 或完成时输出 - if (percent - lastPercent >= 2.0 || processed == total) - { - lastPercent = percent; - Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)"); - } - } - - } - - - - - return ResponseOutput.Ok(); - } - - public class DicomSOPInfo - { - public Guid InstanceId { get; set; } - - - public string TransferSyntaxUID { get; set; } - - public string SOPClassUID { get; set; } - - public string MediaStorageSOPClassUID { get; set; } - - public string MediaStorageSOPInstanceUID { get; set; } - - public bool IsEncapsulated => DicomTransferSyntax.Lookup(DicomUID.Parse(TransferSyntaxUID)).IsEncapsulated; - - - - } - - - [AllowAnonymous] - public async Task ReadExcelImageDataInstanceIsReading([FromServices] IRepository _instanceRepository, - [FromServices] IRepository _seriesRepository, - [FromServices] IRepository _studyRepository) - { - - var trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091"); - - var rows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\instanceReading.xlsx"); - - rows = rows.Where(t => !string.IsNullOrEmpty(t.SopInstanceUid) && t.SopInstanceUid.Length > 15).ToList(); - - var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt"); - - - //foreach (var batch in rows.Chunk(20)) - //{ - // var sopUids = batch.Select(x => x.SopInstanceUid).ToList(); - - // try - // { - // await _instanceRepository.BatchUpdateNoTrackingAsync( - // t => sopUids.Contains(t.SopInstanceUid) && t.TrialId == trialId, - // t => new DicomInstance - // { - // IsReading = true, - // IsDeleted = false - // }, false); - - - // await _seriesRepository.BatchUpdateNoTrackingAsync( - // t => t.DicomInstanceList.Any(t => sopUids.Contains(t.SopInstanceUid)) && t.TrialId == trialId, - // t => new DicomSeries - // { - // IsReading = true, - // IsDeleted = false - // }, false); - // } - // catch (Exception ex) - // { - // var errorMsg = $"{string.Join(",", sopUids)} {DateTime.Now} 批量更新失败: {ex.Message}"; - // Console.WriteLine(errorMsg); - // await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine); - // } - //} - - //找到该项目的检查,实时统计数量,并且回更数据库 - - var studyList = _studyRepository.Where(t => t.TrialId == trialId && (t.SeriesCount != t.SeriesList.Count() || t.InstanceCount != t.InstanceList.Count())) - .Select(t => new - { - t.Id, - t.StudyCode, - DBSeriesCount = t.SeriesCount, - DBInstanceCount = t.InstanceCount, - - ActrualSeriesCount = t.SeriesList.Count(), - - ActrualInstanceCount = t.InstanceList.Count(), - - }).ToList(); - - - var seriesList = _seriesRepository.Where(t => t.TrialId == trialId && t.InstanceCount != t.DicomInstanceList.Count()) - .Select(t => new - { - SeriesId = t.Id, - t.DicomStudy.StudyCode, - DBInstanceCount = t.InstanceCount, - ActrualInstanceCount = t.DicomInstanceList.Count(), - - }).ToList(); - - - await File.AppendAllTextAsync(outputErrorFile, studyList.ToJsonStr() + Environment.NewLine); - - await File.AppendAllTextAsync(outputErrorFile, seriesList.ToJsonStr() + Environment.NewLine); - - return ResponseOutput.Ok(); - } - - public class DicomSOPInstanceInfo - { - public string SopInstanceUid { get; set; } - - - } - - #endregion - - - #region 通过Excel 读取未下载的,边下载边维护数据 - - [AllowAnonymous] - public async Task WriteNeedDealData([FromServices] IRepository _instanceRepository) - { - - #region 获取差集数据 - //var rows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\维护数据读取.xlsx"); - - //rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - //var allRows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\AllData.xlsx"); - - //allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - //var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList(); - - //var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt"); - - //foreach (var item in needDealRows) - //{ - // var line = string.Join(",", item.InstanceId, item.Path); - - // await File.AppendAllTextAsync(outputFile, line + Environment.NewLine); - //} - - #endregion - - var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment); - - var needDealRows = await MiniExcel.QueryAsync(Path.Combine(folder, "needDownload.xlsx")); - - needDealRows = needDealRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - - - var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt"); - - var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt"); - - int total = needDealRows.Count(); - - Console.WriteLine($"需要处理数量{total}"); - - int processed = 0; - double lastPercent = 0; - - foreach (var item in needDealRows) - { - - try - { - await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path); - - var dicomFile = DicomFile.Open(stream); - - var dataset = dicomFile.Dataset; - var fileMeta = dicomFile.FileMetaInfo; - - var pixelData = DicomPixelData.Create(dicomFile.Dataset); - - //获取像素是否为封装形式 - var syntax = dicomFile.Dataset.InternalTransferSyntax; - - var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty); - - var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty); - - var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty); - - //PatientID TrialCode_SubjectCode - //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty); - - //SubjectCode - var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty); - //访视visitNum - var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty); - var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty); - var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty); - var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty); - - var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1); - - - // 传输语法 - var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty); - var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty); - var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty); - - var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty); - - - var line = string.Join(",", - item.InstanceId, - stationName, - institutionName, - manufacturer, - clinicalTrialSubjectID, - clinicalTrialTimePointID, - studyInstanceUID, - seriesInstanceUID, - sOPInstanceUID, - numberOfFrames, - transferSyntaxUID, - sOPClassUID, - mediaStorageSOPClassUID, - mediaStorageSOPInstanceUID - - ); - - - await File.AppendAllTextAsync(outputFile, line + Environment.NewLine); - - //维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID - await _instanceRepository.BatchUpdateNoTrackingAsync( - t => t.Id == item.InstanceId, - t => new DicomInstance - { - IsEncapsulated = syntax.IsEncapsulated, - TransferSytaxUID = transferSyntaxUID, - MediaStorageSOPClassUID = mediaStorageSOPClassUID, - SOPClassUID = sOPClassUID, - MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID - }, false); - } - catch (Exception ex) - { - - var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}"; - - Console.WriteLine(errorMsg); - - await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine); - } - finally - { - processed++; - double percent = processed * 100.0 / total; - - // 每提升 5% 或完成时输出 - if (percent - lastPercent >= 2.0 || processed == total) - { - lastPercent = percent; - Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)"); - } - } - - - } - - return ResponseOutput.Ok(); - } - - public class NeedDealInstanceInfo - { - public Guid InstanceId { get; set; } - - public string Path { get; set; } - } - - /// - /// 读取该项目的数据,进行维护 - /// - /// - /// - /// - [AllowAnonymous] - public async Task WriteTrialNeedDealData([FromServices] IRepository _instanceRepository, Guid trialId) - { - - #region 获取差集数据 - //var rows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\维护数据读取.xlsx"); - - //rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - //var allRows = await MiniExcel.QueryAsync(@"C:\Users\hang\Desktop\AllData.xlsx"); - - //allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList(); - - //var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList(); - - //var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt"); - - //foreach (var item in needDealRows) - //{ - // var line = string.Join(",", item.InstanceId, item.Path); - - // await File.AppendAllTextAsync(outputFile, line + Environment.NewLine); - //} - - #endregion - - var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment); - - var needDealRows = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "").Select(t => new NeedDealInstanceInfo() { InstanceId = t.Id, Path = t.Path }); - - - - var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt"); - - var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt"); - - int total = needDealRows.Count(); - - Console.WriteLine($"需要处理数量{total}"); - - int processed = 0; - double lastPercent = 0; - - foreach (var item in needDealRows) - { - - try - { - await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path); - - var dicomFile = DicomFile.Open(stream); - - var dataset = dicomFile.Dataset; - var fileMeta = dicomFile.FileMetaInfo; - - var pixelData = DicomPixelData.Create(dicomFile.Dataset); - - //获取像素是否为封装形式 - var syntax = dicomFile.Dataset.InternalTransferSyntax; - - var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty); - - var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty); - - var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty); - - //PatientID TrialCode_SubjectCode - //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty); - - //SubjectCode - var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty); - //访视visitNum - var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty); - var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty); - var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty); - var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty); - - var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1); - - - // 传输语法 - var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty); - var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty); - var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty); - - var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty); - - - var line = string.Join(",", - item.InstanceId, - stationName, - institutionName, - manufacturer, - clinicalTrialSubjectID, - clinicalTrialTimePointID, - studyInstanceUID, - seriesInstanceUID, - sOPInstanceUID, - numberOfFrames, - transferSyntaxUID, - sOPClassUID, - mediaStorageSOPClassUID, - mediaStorageSOPInstanceUID - - ); - - - await File.AppendAllTextAsync(outputFile, line + Environment.NewLine); - - //维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID - await _instanceRepository.BatchUpdateNoTrackingAsync( - t => t.Id == item.InstanceId, - t => new DicomInstance - { - IsEncapsulated = syntax.IsEncapsulated, - TransferSytaxUID = transferSyntaxUID, - MediaStorageSOPClassUID = mediaStorageSOPClassUID, - SOPClassUID = sOPClassUID, - MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID - }, false); - } - catch (Exception ex) - { - - var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}"; - - Console.WriteLine(errorMsg); - - await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine); - } - finally - { - processed++; - double percent = processed * 100.0 / total; - - // 每提升 5% 或完成时输出 - if (percent - lastPercent >= 2.0 || processed == total) - { - lastPercent = percent; - Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)"); - } - } - - - } - - return ResponseOutput.Ok(); - } - #endregion - - - #region oss 下载删除影像 ,并且恢复数据 - [AllowAnonymous] - public async Task RestoreDBOSSDate( - [FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment, [FromServices] IRepository _studyRepository) - { - var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment); - - var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKey_info.txt"); - var outputFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyExport_info.txt"); - - var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerror.txt"); - var outputErrorFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerrorStudy.txt"); - - - var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS; - - var tempToken = _oSSService.GetObjectStoreTempToken(); - - var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, - tempToken.AliyunOSS.AccessKeyId, - tempToken.AliyunOSS.AccessKeySecret, - tempToken.AliyunOSS.SecurityToken); - - - var allVersions = new List(); - var allDeleteMarkers = new List(); - - var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName) - { - Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image", - //Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3", - MaxKeys = 1000, - }; - - ObjectVersionList result; - do - { - - result = _ossClient.ListObjectVersions(request); - - if (result.ObjectVersionSummaries != null) - allVersions.AddRange(result.ObjectVersionSummaries); - - if (result.DeleteMarkerSummaries != null) - allDeleteMarkers.AddRange(result.DeleteMarkerSummaries); - - request.KeyMarker = result.NextKeyMarker; - request.VersionIdMarker = result.NextVersionIdMarker; - - } while (result.IsTruncated); - - Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记"); - - - - int total = allDeleteMarkers.Count; - - int processed = 0; - double lastPercent = 0; - - - // 按 Key 分组,找每个删除标记前的最近版本 - var versionsByKey = allVersions - .GroupBy(v => v.Key) - .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList()); - - foreach (var del in allDeleteMarkers) - { - #region 防止阿里云过期 - if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now) - { - tempToken = _oSSService.GetObjectStoreTempToken(); - - _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, - tempToken.AliyunOSS.AccessKeyId, - tempToken.AliyunOSS.AccessKeySecret, - tempToken.AliyunOSS.SecurityToken); - } - - #endregion - - if (!versionsByKey.TryGetValue(del.Key, out var versions)) - continue; // 没有历史版本无法恢复 - - var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified); - if (prevVersion == null) - continue; // 没找到可恢复版本 - - if (Path.GetExtension(prevVersion.Key).IsNotNullOrEmpty()) - { - continue;//不是dicom 文件 - } - - - try - { - //await File.AppendAllTextAsync(outputFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine); - - var getReq = new GetObjectRequest(tempToken.AliyunOSS.BucketName, prevVersion.Key) - { - VersionId = prevVersion.VersionId - }; - - using (var getResult = _ossClient.GetObject(getReq)) - using (var memStream = new MemoryStream()) - { - // 先把 OSS 流复制到内存流 - getResult.Content.CopyTo(memStream); - memStream.Position = 0; - - // 读取 DICOM 信息 - var dicomFile = DicomFile.Open(memStream); - var studyInstanceUID = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID); - - var findInfo = await _studyRepository.Where(t => t.StudyInstanceUid == studyInstanceUID && t.TrialId == Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091")) - .Select(t => new { t.StudyInstanceUid, t.Subject.Code, t.SubjectVisit.VisitName, t.SubjectId, t.SubjectVisitId }).FirstOrDefaultAsync(); - - if (findInfo != null) - { - - // 再保存到另一个路径(可以使用 fo-dicom 保存) - - var fileName = Path.GetFileNameWithoutExtension(prevVersion.Key); - var anotherPath = Path.Combine(folder, findInfo.Code, findInfo.VisitName, studyInstanceUID, fileName); - // 去掉 folder 部分,得到相对路径 - var relativePath = Path.GetRelativePath(folder, anotherPath); - Directory.CreateDirectory(Path.GetDirectoryName(anotherPath)); - dicomFile.Save(anotherPath); - - await File.AppendAllTextAsync(outputFile2, $"{findInfo.SubjectId},{findInfo.SubjectVisitId},{prevVersion.Key},{prevVersion.VersionId},{relativePath},{findInfo.Code},{findInfo.VisitName},{findInfo.StudyInstanceUid},{fileName}" + Environment.NewLine); - } - else - { - await File.AppendAllTextAsync(outputErrorFile2, $"{studyInstanceUID},{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine); - } - - //Console.WriteLine($"读取到 studyInstanceUID: {studyInstanceUID}"); - - //var localPath = Path.Combine(folder, prevVersion.Key.Trim('/').Replace('/', Path.DirectorySeparatorChar)); - //Directory.CreateDirectory(Path.GetDirectoryName(localPath)); - //// 保存到原本路径 - //memStream.Position = 0; - //using (var fs = File.Create(localPath)) - //{ - // memStream.CopyTo(fs); - //} - - - } - - - - //Console.WriteLine($"✅ 下载成功: {prevVersion.Key} (version={prevVersion.VersionId})"); - } - catch (Exception ex) - { - Console.WriteLine($"❌ 下载失败: {prevVersion.Key}, 错误: {ex.Message}"); - - await File.AppendAllTextAsync(outputErrorFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine); - } - finally - { - processed++; - double percent = processed * 100.0 / total; - - // 每提升 5% 或完成时输出 - if (percent - lastPercent >= 2.0 || processed == total) - { - lastPercent = percent; - Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)"); - } - } - - - // 使用 CopyObject 把历史版本拷贝为最新版本(恢复) - //var copyReq = new CopyObjectRequest - //{ - // Bucket = bucketName, - // Key = prevVersion.Key, - // SourceBucket = bucketName, - // SourceKey = prevVersion.Key, - // SourceVersionId = prevVersion.VersionId - //}; - - //try - //{ - // var copyResult = client.CopyObject(copyReq); - // Console.WriteLine($"✅ 恢复成功: {prevVersion.Key} -> newVersionId={copyResult.VersionId}"); - //} - //catch (Exception ex) - //{ - // Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}"); - //} - } - - - - return ResponseOutput.Ok(); - } - - - public async Task OSSDeleteReStorre([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment) { var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;