临时token 续期以服务器时间比较
continuous-integration/drone/push Build is running
Details
continuous-integration/drone/push Build is running
Details
parent
99b6c63de7
commit
bb3f612772
|
|
@ -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<StudyDIRInfo> list, Dictionary<string, string> dic,string dirSavePath)
|
||||
public static async Task GenerateStudyDIR(List<StudyDIRInfo> list, Dictionary<string, string> dic, string? dirSavePath = null, Stream? outputStream = null)
|
||||
{
|
||||
var mappings = new List<string>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -948,7 +948,7 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> 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<ObjectStoreServiceOptions> 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<ObjectStoreServiceOptions> options,
|
|||
lock (_tokenLock)
|
||||
{
|
||||
if (AWSTempToken == null ||
|
||||
AWSTempToken.Expiration <= DateTime.UtcNow.AddMinutes(15))
|
||||
AWSTempToken.Expiration <= DateTime.Now.AddMinutes(15))
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Stream, Task>? CustomWriter { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private async Task CreateVisitZipAsync(string zipPath, List<ZipItem> 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<IResponseOutput> 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<SubjectVisitExcel>(Path.Combine(rootFolder, "Old.xlsx")).ToList();
|
||||
|
||||
var downloadVisits = MiniExcel.Query<SubjectVisitExcel>(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<SubjectVisitExcel>(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<ZipItem>();
|
||||
|
||||
var visitFolderName = $"{visitItem.SubjectCode}_{visitItem.VisitName.Trim()}";
|
||||
|
||||
foreach (var studyInfo in visitItem.StudyList)
|
||||
{
|
||||
var dirDic = new Dictionary<string, string>();
|
||||
|
||||
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
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载影像 维护dir信息 并回传到OSS
|
||||
|
|
|
|||
Loading…
Reference in New Issue