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, 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 a23703c16..ef8607b24 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,30 +101,101 @@ 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 bool IsZip { get; set; } + public string Name { get; set; } + + + public Func Action { get; set; } + } + /// /// 后端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 { 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 +229,268 @@ namespace IRaCIS.Core.Application.Service file.FileType }).ToList() }).ToList() - }).ToList() + }).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList() }).FirstOrDefault(); - - 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(); - - Console.WriteLine($"下载总数量:{count}+{count2}={count + count2}"); - - if (downloadInfo != null) + if (downloadInfo == null) { - var downloadJobs = new List>(); + return ResponseOutput.Ok(); + } - //var rootFolder = @"E:\DownloadImage"; + #region 设置目录 - var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment); + var rootFolder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment); + Directory.CreateDirectory(rootFolder); - // 获取无效字符(系统定义的) - string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); + // 获取无效字符(系统定义的) + string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); - // 用正则表达式替换所有非法字符为下划线或空字符 - string pattern = $"[{Regex.Escape(invalidChars)}]"; + // 用正则表达式替换所有非法字符为下划线或空字符 + string pattern = $"[{Regex.Escape(invalidChars)}]"; - var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_"); + var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_"); - // 创建一个临时文件夹来存放文件 - string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}"); - Directory.CreateDirectory(trialFolderPath); + // 创建一个临时文件夹来存放文件 + string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}");//_{NewId.NextGuid()} + Directory.CreateDirectory(trialFolderPath); - foreach (var visitItem in downloadInfo.VisitList) + 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(); + + #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() { - if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) + StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312")) + }).ToList(); + + 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 - startIndex + 1).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(); + + Log.Logger.Warning($"下载总数量:{count}+{count2}={count + count2}"); + + var downloadJobs = new List(); + + foreach (var visitItem in acturalDownList) + { + 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.Trim()}", $"{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.Trim()}_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.Trim()}", $"{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.Trim()}_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.Trim()}"); - //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.Trim()}_Zip", - // await Task.WhenAll(batch); + IsZip = true, - // downloadedCount += batch.Count(); + Action = async () => + { + string zipPath = visitFolderPath + ".zip"; - // Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); - //} - #endregion + 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; + + 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 + + #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();