From 1b7bb5a4fe43e83baa970914e20f00efe066d8fd Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 25 Feb 2025 16:11:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=93=BE=E6=8E=A5=E8=BF=87?= =?UTF-8?q?=E6=9C=9F+=20=E5=88=A0=E9=99=A4oss=20=E6=95=B0=E6=8D=AEok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ExtraController.cs | 6 +- IRaCIS.Core.API/appsettings.Test_IRC.json | 3 +- IRaCIS.Core.API/appsettings.json | 2 +- IRaCIS.Core.Application/Auth/JwtSetting.cs | 2 +- IRaCIS.Core.Application/Auth/TokenService.cs | 18 ++- IRaCIS.Core.Application/Helper/OSSService.cs | 106 +++++++++++++++++- .../IRaCIS.Core.Application.xml | 4 +- .../ImageAndDoc/DownloadAndUploadService.cs | 31 +++-- .../Service/QC/QCOperationService.cs | 36 ++---- 9 files changed, 161 insertions(+), 47 deletions(-) diff --git a/IRaCIS.Core.API/Controllers/ExtraController.cs b/IRaCIS.Core.API/Controllers/ExtraController.cs index 79b7ad5d0..ac6592bd8 100644 --- a/IRaCIS.Core.API/Controllers/ExtraController.cs +++ b/IRaCIS.Core.API/Controllers/ExtraController.cs @@ -186,7 +186,7 @@ namespace IRaCIS.Api.Controllers [HttpGet("User/UserRedirect")] [AllowAnonymous] - public async Task UserRedirect([FromServices] IRepository _useRepository, string url, [FromServices] ILogger _logger) + public async Task UserRedirect([FromServices] IRepository _useRepository, string url, [FromServices] ILogger _logger, [FromServices] ITokenService _tokenService) { var decodeUrl = System.Web.HttpUtility.UrlDecode(url); @@ -212,7 +212,9 @@ namespace IRaCIS.Api.Controllers CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.en_US); } - if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd)) + var isExpire = _tokenService.IsTokenExpired(token); + + if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire) { decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} "; } diff --git a/IRaCIS.Core.API/appsettings.Test_IRC.json b/IRaCIS.Core.API/appsettings.Test_IRC.json index 1b3af9b10..7c9246cbd 100644 --- a/IRaCIS.Core.API/appsettings.Test_IRC.json +++ b/IRaCIS.Core.API/appsettings.Test_IRC.json @@ -23,7 +23,8 @@ "AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV", "RoleArn": "acs:ram::1899121822495495:role/dev-oss-access", "BucketName": "zy-irc-test-store", - "ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com", + //"ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com", + "ViewEndpoint": "https://zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com", "Region": "oss-cn-shanghai", "DurationSeconds": 7200 }, diff --git a/IRaCIS.Core.API/appsettings.json b/IRaCIS.Core.API/appsettings.json index c575d4113..60b835b4a 100644 --- a/IRaCIS.Core.API/appsettings.json +++ b/IRaCIS.Core.API/appsettings.json @@ -3,7 +3,7 @@ "SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021", "Issuer": "Extimaging", "Audience": "EICS", - "TokenExpireDays": "7" + "TokenExpireMinute": "5" }, "IpRateLimiting": { "EnableEndpointRateLimiting": true, diff --git a/IRaCIS.Core.Application/Auth/JwtSetting.cs b/IRaCIS.Core.Application/Auth/JwtSetting.cs index cc54eb4d0..52148b7a0 100644 --- a/IRaCIS.Core.Application/Auth/JwtSetting.cs +++ b/IRaCIS.Core.Application/Auth/JwtSetting.cs @@ -23,7 +23,7 @@ namespace IRaCIS.Core.Application.Auth /// /// 过期时间 /// - public int TokenExpireDays { get; set; } + public int TokenExpireMinute { get; set; } //public Dictionary Claims { get; set; } diff --git a/IRaCIS.Core.Application/Auth/TokenService.cs b/IRaCIS.Core.Application/Auth/TokenService.cs index 3a06e579c..6b4f908a4 100644 --- a/IRaCIS.Core.Application/Auth/TokenService.cs +++ b/IRaCIS.Core.Application/Auth/TokenService.cs @@ -9,6 +9,8 @@ namespace IRaCIS.Core.Application.Auth public interface ITokenService { string GetToken(UserTokenInfo user); + + bool IsTokenExpired(string token); } @@ -47,13 +49,27 @@ namespace IRaCIS.Core.Application.Auth signingCredentials: _jwtSetting.Credentials, claims: claims, notBefore: DateTime.Now, - expires: DateTime.Now.AddDays(_jwtSetting.TokenExpireDays) + expires: DateTime.Now.AddMinutes(_jwtSetting.TokenExpireMinute) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; } + + public bool IsTokenExpired(string token) + { + var handler = new JwtSecurityTokenHandler(); + try + { + var jwtToken = handler.ReadJwtToken(token); + return jwtToken.ValidTo < DateTime.UtcNow; + } + catch + { + return true; // 无效 Token 也视为已过期 + } + } } diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 53bf6338a..22eaf9f1e 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -9,8 +9,10 @@ using Amazon.SecurityToken.Model; using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure.NewtonsoftJson; using MassTransit; +using MassTransit.Caching.Internals; using Microsoft.Extensions.Options; using Minio; +using Minio.DataModel; using Minio.DataModel.Args; using System.Reactive.Linq; using System.Runtime.InteropServices; @@ -143,7 +145,9 @@ public interface IOSSService public Task GetSignedUrl(string ossRelativePath); - public Task DeleteFromPrefix(string prefix); + public Task DeleteFromPrefix(string prefix, bool isCache = false); + + public Task DeleteObjects(List objectKeys); List GetRootFolderNames(); @@ -550,9 +554,9 @@ public class OSSService : IOSSService do { // 列出根目录下的对象和文件夹 - objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName) + objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName) { - + MaxKeys = 1000, Marker = nextMarker, Delimiter = "/" // 使用分隔符来模拟文件夹 @@ -582,7 +586,7 @@ public class OSSService : IOSSService /// /// /// - public async Task DeleteFromPrefix(string prefix) + public async Task DeleteFromPrefix(string prefix, bool isCache = false) { GetObjectStoreTempToken(); @@ -592,6 +596,21 @@ public class OSSService : IOSSService var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken); + var bucketName = string.Empty; + + if (isCache) + { + Uri uri = new Uri(aliConfig.ViewEndpoint); + string host = uri.Host; // 获取 "zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com" + string[] parts = host.Split('.'); + bucketName = parts[0]; + } + else + { + bucketName = aliConfig.BucketName; + } + + try { @@ -600,7 +619,7 @@ public class OSSService : IOSSService do { // 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker - objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName) + objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(bucketName) { Prefix = prefix, MaxKeys = 1000, @@ -612,7 +631,7 @@ public class OSSService : IOSSService // 删除获取到的文件 if (keys.Count > 0) { - _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false)); + _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(bucketName, keys, false)); } // 设置 NextMarker 以获取下一页的数据 @@ -721,6 +740,81 @@ public class OSSService : IOSSService } } + public async Task DeleteObjects(List objectKeys) + { + GetObjectStoreTempToken(); + + if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS") + { + var aliConfig = ObjectStoreServiceOptions.AliyunOSS; + + var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken); + + if (objectKeys.Count > 0) + { + var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, objectKeys, false)); + + } + } + else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO") + { + var minIOConfig = ObjectStoreServiceOptions.MinIO; + + + var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}") + .WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL) + .Build(); + + + if (objectKeys.Count > 0) + { + var objArgs = new RemoveObjectsArgs() + .WithBucket(minIOConfig.BucketName) + .WithObjects(objectKeys); + + // 删除对象 + await minioClient.RemoveObjectsAsync(objArgs); + } + } + else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS") + { + + var awsConfig = ObjectStoreServiceOptions.AWS; + + + // 提供awsAccessKeyId和awsSecretAccessKey构造凭证 + var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken); + + //提供awsEndPoint(域名)进行访问配置 + var clientConfig = new AmazonS3Config + { + RegionEndpoint = RegionEndpoint.USEast1, + UseHttp = true, + }; + + var amazonS3Client = new AmazonS3Client(credentials, clientConfig); + + if (objectKeys.Count > 0) + { + // 准备删除请求 + var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest + { + BucketName = awsConfig.BucketName, + Objects = objectKeys.Select(t => new KeyVersion() { Key = t }).ToList() + }; + + + // 批量删除对象 + var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest); + } + + } + else + { + throw new BusinessValidationFailedException("未定义的存储介质类型"); + } + } + public ObjectStoreDTO GetObjectStoreTempToken() { diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 0d1c6e36a..f787f3caf 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -19,7 +19,7 @@ 令牌密码 - + 过期时间 @@ -13469,7 +13469,7 @@ - + 删除某个目录的文件 diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index 19df69976..494022bf3 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -44,7 +44,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc IRepository _noneDicomStudyFileReposiotry, IDistributedLockProvider _distributedLockProvider, IRepository _trialImageDownloadRepository, - IRepository _subjectRepository, + IRepository _subjectRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, IDownloadAndUploadService { @@ -257,7 +257,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } - [TrialGlobalLimit( "AfterStopCannNotOpt" )] + [TrialGlobalLimit("AfterStopCannNotOpt")] public async Task PreArchiveDicomStudy(PriArchiveTaskStudyCommand preArchiveStudyCommand) { @@ -356,7 +356,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } - [TrialGlobalLimit( "AfterStopCannNotOpt" )] + [TrialGlobalLimit("AfterStopCannNotOpt")] public async Task AddOrUpdateArchiveTaskStudy(TaskArchiveStudyCommand incommand) { #region 获取该subject 已生成任务的访视的检查 @@ -620,12 +620,25 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc { if (dicomStudyId == null) { + var deleteStudyPathList = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId).GroupBy(t => t.StudyId).Select(g => g.First().Path).ToListAsync(); + + foreach (var fisrtPath in deleteStudyPathList) + { + var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1); + await _oSSService.DeleteFromPrefix(prefix, true); + } + + await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId); await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId); await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId); } else { + var fisrtPath = await _taskInstanceRepository.Where(t => t.VisitTaskId == visitTaskId && t.StudyId == dicomStudyId).Select(t => t.Path).FirstOrDefaultAsync(); + var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1); + await _oSSService.DeleteFromPrefix(prefix, true); + await _taskStudyRepository.DeleteFromQueryAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId); await _taskSeriesRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId); await _taskInstanceRepository.BatchDeleteNoTrackingAsync(t => t.VisitTaskId == visitTaskId && t.Id == dicomStudyId); @@ -689,7 +702,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc SubjectCode = u.IsSelfAnalysis == true ? u.BlindSubjectCode : u.Subject.Code, TaskBlindName = u.TaskBlindName, TaskName = u.TaskName, - ReadingTaskState=u.ReadingTaskState, + ReadingTaskState = u.ReadingTaskState, SourceSubjectVisitId = u.SourceSubjectVisitId, VisitTaskId = u.Id, @@ -761,9 +774,9 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc select new { TrialId = sv.TrialId, - SubjectId=sv.SubjectId, + SubjectId = sv.SubjectId, SubjectCode = sv.Subject.Code, - TrialSiteCode=sv.TrialSite.TrialSiteCode, + TrialSiteCode = sv.TrialSite.TrialSiteCode, VisitName = sv.VisitName, StudyList = sv.StudyList.Where(t => isQueryDicom ? inQuery.DicomStudyIdList.Contains(t.Id) : false) @@ -971,7 +984,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc var subjectVisitIdList = inQuery.SubjectVisitTaskList.Select(t => t.SubjectVisitId).ToList(); - var trialSiteCode = _visitTaskRepository.Where(t => t.Id == taskIdList.FirstOrDefault()).Select(t => t.IsAnalysisCreate ? t.BlindTrialSiteCode : t.Subject.TrialSite.TrialSiteCode).FirstOrDefault()??string.Empty; + var trialSiteCode = _visitTaskRepository.Where(t => t.Id == taskIdList.FirstOrDefault()).Select(t => t.IsAnalysisCreate ? t.BlindTrialSiteCode : t.Subject.TrialSite.TrialSiteCode).FirstOrDefault() ?? string.Empty; var query = from sv in _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id))) //一致性分析,导致查询出来两条数据 @@ -1032,9 +1045,9 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc { Id = NewId.NextSequentialGuid(), TrialId = info.TrialId, - SubjectId=inQuery.SubjectId, + SubjectId = inQuery.SubjectId, SubjectCode = inQuery.SubjectCode, - TrialSiteCode= trialSiteCode, + TrialSiteCode = trialSiteCode, IP = _userInfo.IP, DownloadStartTime = DateTime.Now, IsSuccess = false, diff --git a/IRaCIS.Core.Application/Service/QC/QCOperationService.cs b/IRaCIS.Core.Application/Service/QC/QCOperationService.cs index e0a1d5f3c..5becbc155 100644 --- a/IRaCIS.Core.Application/Service/QC/QCOperationService.cs +++ b/IRaCIS.Core.Application/Service/QC/QCOperationService.cs @@ -35,6 +35,7 @@ namespace IRaCIS.Core.Application.Image.QA IRepository _trialQCQuestionAnswerRepository, IRepository _readingQuestionCriterionTrialRepository, IDistributedLockProvider _distributedLockProvider, IReadingClinicalDataService _readingClinicalDataService, + IOSSService _oSSService, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IWebHostEnvironment _hostEnvironment) : BaseService, IQCOperationService { @@ -717,7 +718,7 @@ namespace IRaCIS.Core.Application.Image.QA var study = (await _dicomStudyRepository.Where(t => t.Id == series.StudyId, true).IgnoreQueryFilters().FirstOrDefaultAsync()).IfNullThrowException(); study.InstanceCount = study.InstanceCount + 1; - + } @@ -835,6 +836,8 @@ namespace IRaCIS.Core.Application.Image.QA await _dicomStudyRepository.DeleteAsync(study); + var fisrtPath = await _dicomInstanceRepository.Where(t => t.StudyId == id).Select(t => t.Path).FirstOrDefaultAsync(); + var succeess2 = await _dicomInstanceRepository.BatchDeleteNoTrackingAsync(t => t.StudyId == id); var success3 = await _dicomSeriesrepository.BatchDeleteNoTrackingAsync(t => t.StudyId == id); @@ -842,6 +845,9 @@ namespace IRaCIS.Core.Application.Image.QA await _scpStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == id, u => new SCPStudy() { SubjectVisitId = null }); + var prefix = fisrtPath.Substring(1, fisrtPath.LastIndexOf('/') - 1); + + await _oSSService.DeleteFromPrefix(prefix, true); //var success3 = await _dicomSeriesrepository.DeleteFromQueryAsync(t => t.StudyId == id, true); //var success4 = await _repository.BatchDeleteAsync(t => t.StudyId == id); @@ -950,7 +956,7 @@ namespace IRaCIS.Core.Application.Image.QA { var nextIQCQuality = await this.GetNextIQCQuality(inDto); - if (nextIQCQuality.VisitId != null&& nextIQCQuality.IsReceived==false) + if (nextIQCQuality.VisitId != null && nextIQCQuality.IsReceived == false) { var visit = await _subjectVisitRepository.Where(x => x.Id == nextIQCQuality.VisitId).FirstNotNullAsync(); if (!visit.IsTake) @@ -988,7 +994,7 @@ namespace IRaCIS.Core.Application.Image.QA return new GetNextIQCQualityOutDto() { IsReceived = true, - SubjectId= currentActionList[0].SubjectId, + SubjectId = currentActionList[0].SubjectId, VisitId = currentActionList[0].Id, }; @@ -1627,7 +1633,7 @@ namespace IRaCIS.Core.Application.Image.QA //删除 软删除的物理文件 - var instancePathList = await _dicomInstanceRepository.Where(t => t.DicomSerie.IsDeleted && t.SubjectVisitId == subjectVisitId) + var instancePathList = await _dicomInstanceRepository.Where(t => (t.DicomSerie.IsDeleted || t.IsDeleted) && t.SubjectVisitId == subjectVisitId, false, true) .Select(t => t.Path).ToListAsync(); //维护统一状态 @@ -1666,17 +1672,9 @@ namespace IRaCIS.Core.Application.Image.QA dbSubjectVisit.ReadingStatus = trialConfig.IsImageConsistencyVerification ? ReadingStatusEnum.ConsistencyCheck : ReadingStatusEnum.TaskAllocate; //删除影像 - instancePathList.ForEach(path => - { - var physicalPath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, path); + await _oSSService.DeleteObjects(instancePathList.Select(t => t.TrimStart('/')).ToList()); - if (System.IO.File.Exists(physicalPath)) - { - File.Delete(physicalPath); - } - - }); } else @@ -1730,17 +1728,7 @@ namespace IRaCIS.Core.Application.Image.QA dbSubjectVisit.ReadingStatus = trialConfig.IsImageConsistencyVerification ? ReadingStatusEnum.ConsistencyCheck : ReadingStatusEnum.TaskAllocate; //删除影像 - instancePathList.ForEach(path => - { - - var physicalPath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, path); - - if (System.IO.File.Exists(physicalPath)) - { - File.Delete(physicalPath); - } - - }); + await _oSSService.DeleteObjects(instancePathList.Select(t => t.TrimStart('/')).ToList()); } else