diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index ae03f93a3..993353891 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -1,6 +1,7 @@ using IRaCIS.Core.API; using IRaCIS.Core.API.HostService; using IRaCIS.Core.Application.BusinessFilter; +using IRaCIS.Core.Application.BusinessFilter.LegacyController.Database.Api; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.MassTransit.Consumer; using IRaCIS.Core.Application.Service; @@ -100,6 +101,7 @@ builder.Services.AddControllers(options => options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); + //options.Filters.Add(); }) .AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理 diff --git a/IRaCIS.Core.API/_ServiceExtensions/ServiceCollectionSetup.cs b/IRaCIS.Core.API/_ServiceExtensions/ServiceCollectionSetup.cs index 7541ecf25..075a7fd9e 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/ServiceCollectionSetup.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/ServiceCollectionSetup.cs @@ -28,6 +28,7 @@ public static class ServiceCollectionSetup services.AddOptions().Configure(_configuration.GetSection("AliyunOSS")); services.AddOptions().Configure(_configuration.GetSection("ObjectStoreService")); services.AddOptions().Configure(_configuration.GetSection("EncrypteResponseConfig")); + services.AddOptions().Configure(_configuration.GetSection("RequestDuplicationOptions")); services.AddOptions().Configure(_configuration.GetSection("SystemPacsConfig")); services.Configure(_configuration.GetSection("IRaCISBasicConfig")); diff --git a/IRaCIS.Core.API/appsettings.Event_IRC.json b/IRaCIS.Core.API/appsettings.Event_IRC.json index 392b55d49..6ec66b5b2 100644 --- a/IRaCIS.Core.API/appsettings.Event_IRC.json +++ b/IRaCIS.Core.API/appsettings.Event_IRC.json @@ -69,7 +69,13 @@ "CompanyShortName": "Extensive Imaging", "CompanyShortNameCN": "展影医疗", "IsEnv_US": false + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } - } diff --git a/IRaCIS.Core.API/appsettings.Prod_IRC.json b/IRaCIS.Core.API/appsettings.Prod_IRC.json index b0f6caeed..9985c978b 100644 --- a/IRaCIS.Core.API/appsettings.Prod_IRC.json +++ b/IRaCIS.Core.API/appsettings.Prod_IRC.json @@ -78,6 +78,12 @@ "SystemPacsConfig": { "Port": "11113", "IP": "101.132.193.237" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } - } diff --git a/IRaCIS.Core.API/appsettings.Test_IRC.json b/IRaCIS.Core.API/appsettings.Test_IRC.json index f266ee6b3..a86722863 100644 --- a/IRaCIS.Core.API/appsettings.Test_IRC.json +++ b/IRaCIS.Core.API/appsettings.Test_IRC.json @@ -160,5 +160,16 @@ "Port": "11113", // PACS服务器IP地址 "IP": "106.14.89.110" + }, + // 重复请求配置 + "RequestDuplicationOptions": { + // 是否启用重复请求检测 + "IsEnabled": true, + // 重复请求时间窗口(毫秒) + "DuplicationWindowMs": 500, + // 缓存请求时间(秒) + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } diff --git a/IRaCIS.Core.API/appsettings.Test_IRC_PGSQL.json b/IRaCIS.Core.API/appsettings.Test_IRC_PGSQL.json index 4d17342dc..02bcd1cb8 100644 --- a/IRaCIS.Core.API/appsettings.Test_IRC_PGSQL.json +++ b/IRaCIS.Core.API/appsettings.Test_IRC_PGSQL.json @@ -91,5 +91,12 @@ "SystemPacsConfig": { "Port": "11113", "IP": "106.14.89.110" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } diff --git a/IRaCIS.Core.API/appsettings.US_Prod_IRC.json b/IRaCIS.Core.API/appsettings.US_Prod_IRC.json index 26935cc91..286366cfc 100644 --- a/IRaCIS.Core.API/appsettings.US_Prod_IRC.json +++ b/IRaCIS.Core.API/appsettings.US_Prod_IRC.json @@ -85,6 +85,13 @@ "SystemPacsConfig": { "Port": "104", "IP": "44.210.231.169" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } diff --git a/IRaCIS.Core.API/appsettings.US_Test_IRC.json b/IRaCIS.Core.API/appsettings.US_Test_IRC.json index 043191045..df94467fb 100644 --- a/IRaCIS.Core.API/appsettings.US_Test_IRC.json +++ b/IRaCIS.Core.API/appsettings.US_Test_IRC.json @@ -92,6 +92,13 @@ "SystemPacsConfig": { "Port": "104", "IP": "3.226.182.187" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } diff --git a/IRaCIS.Core.API/appsettings.US_Uat_IRC.json b/IRaCIS.Core.API/appsettings.US_Uat_IRC.json index e303cc381..29856f89e 100644 --- a/IRaCIS.Core.API/appsettings.US_Uat_IRC.json +++ b/IRaCIS.Core.API/appsettings.US_Uat_IRC.json @@ -92,6 +92,12 @@ "SystemPacsConfig": { "Port": "104", "IP": "3.226.182.187" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } - } diff --git a/IRaCIS.Core.API/appsettings.Uat_IRC.json b/IRaCIS.Core.API/appsettings.Uat_IRC.json index c617a54d4..05eaba3c2 100644 --- a/IRaCIS.Core.API/appsettings.Uat_IRC.json +++ b/IRaCIS.Core.API/appsettings.Uat_IRC.json @@ -99,6 +99,13 @@ "SystemPacsConfig": { "Port": "11113", "IP": "101.132.253.119" + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } diff --git a/IRaCIS.Core.API/appsettings.json b/IRaCIS.Core.API/appsettings.json index 4568c3524..66c347795 100644 --- a/IRaCIS.Core.API/appsettings.json +++ b/IRaCIS.Core.API/appsettings.json @@ -74,5 +74,12 @@ "redirect_uri": "https://oauthlogin.net/oauth/githubcallback", "scope": "repo" } + }, + "RequestDuplicationOptions": { + "IsEnabled": true, + "DuplicationWindowMs": 500, + "CacheTimeSeconds": 5, + "ExcludedPaths": [ + ] } } \ No newline at end of file diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/RequestDuplicationFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/RequestDuplicationFilter.cs new file mode 100644 index 000000000..5f30d9315 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/RequestDuplicationFilter.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.BusinessFilter.LegacyController +{ + using Database.Api; + using DocumentFormat.OpenXml.InkML; + using IRaCIS.Core.Application.Service.Common; + using IRaCIS.Core.Infrastructure; + using IRaCIS.Core.Infrastructure.Extention; + using Microsoft.AspNetCore.Components.Endpoints; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Minio.Helper; + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography; + using System.Threading.Tasks; + + namespace Database.Api + { + /// + /// 请求拦截 请求前后的操作 + /// + /// logger + /// loggerHelper + public class RequestDuplicationFilter(ILogger logger, IHttpContextAccessor accessor, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor RequestDuplicationOptionsMonitor) : ExceptionFilterAttribute, IAsyncActionFilter + { + /// + /// 传入的参数 + /// + public string Intoparam { get; set; } + + /// + /// 这个是正常记录(请求刚进入的时候) + /// + /// context + /// next + /// 返回的对象 + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + this.Intoparam = JsonConvert.SerializeObject( + context.ActionArguments.TryGetValue("inQuery", out var v) ? v : null); + try + { + this.RequestDuplication(); + } + catch (Exception) + { + throw; + } + + + var resultContext = await next(); + } + + + + /// + /// + /// + private void RequestDuplication() + { + var requestPath = accessor?.HttpContext?.Request?.Path.ToString() ?? string.Empty; + + // 验证请求频繁情况 + if (_userInfo.UserRoleId != default(Guid)&& + RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled && + !RequestDuplicationOptionsMonitor.CurrentValue.ExcludePaths.Contains(requestPath)) + { + RequestInfo requestInfo = new RequestInfo + { + UserRoleId = _userInfo.UserRoleId, + RequestPath = requestPath, + ParameterHash = GenerateParameterHash(this.Intoparam), + RequestTime = DateTime.Now + }; + + IRCSystemInfo.RequestRecordList= IRCSystemInfo.RequestRecordList.Where(x => x.RequestTime >= DateTime.Now.AddSeconds(-RequestDuplicationOptionsMonitor.CurrentValue.CacheTimeSeconds)).ToList(); + + + var requestsTimes = IRCSystemInfo.RequestRecordList.Any(x=> + x.RequestTime>= requestInfo.RequestTime.AddMilliseconds(-RequestDuplicationOptionsMonitor.CurrentValue.DuplicationWindowMs)&& + x.RequestKey== requestInfo.RequestKey); + if (requestsTimes) + { + throw new BusinessValidationFailedException(_localizer["RequestDuplicationFilter_RequestDuplication"]); + } + IRCSystemInfo.RequestRecordList.Add(requestInfo); + } + } + + public string GenerateParameterHash(string parameters) + { + if (string.IsNullOrEmpty(parameters)) + return string.Empty; + + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(parameters)); + return Convert.ToBase64String(hashBytes); + } + + + + } + } +} diff --git a/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs b/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs index b725834a6..f4bd22935 100644 --- a/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs +++ b/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs @@ -105,6 +105,54 @@ public class IRCEncreptOption public List ApiPathList { get; set; } } +/// +/// 请求缓存配置 +/// +public class RequestDuplicationOptions +{ + /// + /// 缓存时间(秒),默认5秒 + /// + public int CacheTimeSeconds { get; set; } = 5; + + /// + /// 重复请求检测时间窗口(毫秒),默认500毫秒 + /// + public int DuplicationWindowMs { get; set; } = 500; + + /// + /// 是否启用防重复请求 + /// + public bool IsEnabled { get; set; } = true; + + /// + /// 需要排除的路径(不进行重复检测) + /// + public List ExcludePaths { get; set; } = new List(); +} + +public static class IRCSystemInfo +{ + public static List RequestRecordList { get; set; } = new List(); +} + +public class RequestInfo +{ + public Guid UserRoleId { get; set; } + + public string RequestPath { get; set; } = string.Empty; + + public string ParameterHash { get; set; } = string.Empty; + + public DateTime RequestTime { get; set; } + + + /// + /// 请求的唯一标识 + /// + public string RequestKey => $"{UserRoleId}_{RequestPath}_{ParameterHash}"; +} + public class IRaCISBasicConfigOption { public string DoctorCodePrefix { get; set; } diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 5bc700ff6..cc3c6c45b 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -957,6 +957,16 @@ + + + 一致性分析结果导出 7 8 分别是自身 和组件一致性 + + + + + + + 获取阅片标准可以导出的列表 @@ -3223,6 +3233,20 @@ + + + 计算靶病灶状态 + + + + + + + 计算靶病灶融合后状态 + + + + 阅片导入 @@ -13047,7 +13071,7 @@ - 新增修改想想项目表格问题 + 新增修改项目表格问题 @@ -13148,7 +13172,7 @@ - + IR影像阅片 @@ -14388,6 +14412,38 @@ 测试加密API 返回的结果 + + + 请求拦截 请求前后的操作 + + logger + loggerHelper + + + + 请求拦截 请求前后的操作 + + logger + loggerHelper + + + + 传入的参数 + + + + + 这个是正常记录(请求刚进入的时候) + + context + next + 返回的对象 + + + + + + minimal api 生效,但是传统控制器,没生效 @@ -18849,6 +18905,36 @@ + + + 请求缓存配置 + + + + + 缓存时间(秒),默认5秒 + + + + + 重复请求检测时间窗口(毫秒),默认500毫秒 + + + + + 是否启用防重复请求 + + + + + 需要排除的路径(不进行重复检测) + + + + + 请求的唯一标识 + + Id(阅片期Id 或者 访视ID) diff --git a/IRaCIS.Core.Application/Service/Reading/ReadingCriterion/ReadingQuestionService.cs b/IRaCIS.Core.Application/Service/Reading/ReadingCriterion/ReadingQuestionService.cs index 69ffea91d..e416e4a53 100644 --- a/IRaCIS.Core.Application/Service/Reading/ReadingCriterion/ReadingQuestionService.cs +++ b/IRaCIS.Core.Application/Service/Reading/ReadingCriterion/ReadingQuestionService.cs @@ -847,7 +847,7 @@ namespace IRaCIS.Core.Application.Service /// - /// 新增修改想想项目表格问题 + /// 新增修改项目表格问题 /// /// /// @@ -861,6 +861,13 @@ namespace IRaCIS.Core.Application.Service //---问题编号重复 throw new BusinessValidationFailedException(_localizer["ReadingQuestion_IdDup"]); } + if (indto.IsPreinstall) + { + if (await _readingTableQuestionTrialRepository.AnyAsync(x => x.ReadingQuestionId == indto.ReadingQuestionId && x.Id != indto.Id && x.IsPreinstall)) + { + throw new BusinessValidationFailedException(_localizer["ReadingQuestion_OnlyOnePreinstall"]); + } + } indto.ParentTriggerValue = string.Join(',', indto.ParentTriggerValueList); indto.RelevanceValue = string.Join(',', indto.RelevanceValueList); diff --git a/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs b/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs index c8843e03c..62638a459 100644 --- a/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs +++ b/IRaCIS.Core.Application/Service/Reading/ReadingImageTask/ReadingImageTaskService.cs @@ -59,6 +59,7 @@ namespace IRaCIS.Core.Application.Service IRepository _organTrialInfoRepository, IRepository _trialDocumentRepository, ILuganoCalculateService _luganoCalculateService, + ILuganoWithoutPETCalculateService _LuganoWithoutPETCalculateService, //IRepository _readingCustomTagRepository, IRepository _readingTaskQuestionMarkRepository, IRepository _readingTrialCriterionDictionaryRepository, @@ -1794,7 +1795,7 @@ namespace IRaCIS.Core.Application.Service switch (taskInfo.TrialReadingCriterion.CriterionType) { case CriterionType.Lugano2014: - + case CriterionType.Lugano2014WithoutPET: List needSetNa = new List() { QuestionMark.LowPPDAddPercent, @@ -1916,6 +1917,7 @@ namespace IRaCIS.Core.Application.Service switch (taskinfo.TrialReadingCriterion.CriterionType) { case CriterionType.Lugano2014: + await _luganoCalculateService.CalculateMergeTargetLesionStatus(new CalculateTargetLesionStatusInDto() { QuestionId = inDto.QuestionId, @@ -1923,6 +1925,14 @@ namespace IRaCIS.Core.Application.Service RowNumber = mergeRow.RowIndex, }); break; + case CriterionType.Lugano2014WithoutPET: + await _LuganoWithoutPETCalculateService.CalculateMergeTargetLesionStatus(new CalculateTargetLesionStatusInDto() + { + QuestionId = inDto.QuestionId, + VisitTaskId = inDto.VisitTaskId, + RowNumber = mergeRow.RowIndex, + }); + break; } } @@ -1949,6 +1959,7 @@ namespace IRaCIS.Core.Application.Service { case CriterionType.Lugano2014: + case CriterionType.Lugano2014WithoutPET: // (无法评估 或者 状态为消失的非靶病灶) 并且不能是无法评估的病灶 query = query.Where(x => (x.MeasureData == string.Empty || @@ -2252,9 +2263,9 @@ namespace IRaCIS.Core.Application.Service break; case CriterionType.Lugano2014: - + case CriterionType.Lugano2014WithoutPET: // 先找到需要删除的 - + foreach (var item in needDeleteMarkQuestionIds) { // 对于要删除的标记不能删除 要与问题解绑 @@ -2586,6 +2597,7 @@ namespace IRaCIS.Core.Application.Service switch (taskinfo.TrialReadingCriterion.CriterionType) { case CriterionType.Lugano2014: + if (deleteRowInfo.RowIndex % 1 != 0) { await _luganoCalculateService.CalculateTargetLesionStatus(new CalculateTargetLesionStatusInDto() @@ -2596,6 +2608,18 @@ namespace IRaCIS.Core.Application.Service }); } break; + case CriterionType.Lugano2014WithoutPET: + + if (deleteRowInfo.RowIndex % 1 != 0) + { + await _LuganoWithoutPETCalculateService.CalculateTargetLesionStatus(new CalculateTargetLesionStatusInDto() + { + QuestionId = inDto.QuestionId, + VisitTaskId = inDto.VisitTaskId, + RowNumber = deleteRowInfo.RowIndex + }); + } + break; } return ResponseOutput.Ok(true); } @@ -2761,6 +2785,7 @@ namespace IRaCIS.Core.Application.Service } break; case CriterionType.Lugano2014: + case CriterionType.Lugano2014WithoutPET: var targetTablequestionList = await _readingTableQuestionTrialRepository.Where(x => x.TrialCriterionId == taskinfo.TrialReadingCriterionId && x.ReadingQuestionTrial.LesionType == LesionType.TargetLesion).ToListAsync(); @@ -3059,6 +3084,17 @@ namespace IRaCIS.Core.Application.Service }); } break; + case CriterionType.Lugano2014WithoutPET: + if (inDto.RowIndex % 1 != 0) + { + await _LuganoWithoutPETCalculateService.CalculateTargetLesionStatus(new CalculateTargetLesionStatusInDto() + { + QuestionId = inDto.QuestionId, + VisitTaskId = inDto.VisitTaskId, + RowNumber = inDto.RowIndex + }); + } + break; } return result; diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/General/ReadingCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/General/ReadingCalculateService.cs index 61406ff9a..93089a6f5 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/General/ReadingCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/General/ReadingCalculateService.cs @@ -36,6 +36,7 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate {CriterionType.RECIST1Pointt1_MB,typeof(RECIST1Point1_BMCalculateService) }, {CriterionType.IRECIST1Point1,typeof(IRECIST1Point1CalculateService) }, {CriterionType.Lugano2014,typeof(LuganoCalculateService) }, + {CriterionType.Lugano2014WithoutPET,typeof(LuganoWithoutPETCalculateService) }, {CriterionType.IVUS,typeof(IVUSCalculateService) }, {CriterionType.OCT,typeof(OCTCalculateService) }, {CriterionType.MRIPDFF,typeof(MRIPDFFCalculateService) }, diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/Interface/ILuganoWithoutPETCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/Interface/ILuganoWithoutPETCalculateService.cs new file mode 100644 index 000000000..7587ba034 --- /dev/null +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/Interface/ILuganoWithoutPETCalculateService.cs @@ -0,0 +1,21 @@ +using IRaCIS.Core.Application.ViewModel; + +namespace IRaCIS.Core.Application.Service.ReadingCalculate.Interface +{ + public interface ILuganoWithoutPETCalculateService + { + /// + /// 计算靶病灶状态 + /// + /// + /// + Task CalculateTargetLesionStatus(CalculateTargetLesionStatusInDto inDto); + + /// + /// 计算靶病灶融合后状态 + /// + /// + /// + Task CalculateMergeTargetLesionStatus(CalculateTargetLesionStatusInDto inDto); + } +} diff --git a/IRaCIS.Core.Application/Service/ReadingCalculate/LuganoWithoutPETCalculateService.cs b/IRaCIS.Core.Application/Service/ReadingCalculate/LuganoWithoutPETCalculateService.cs index 100debf1a..2aa16011c 100644 --- a/IRaCIS.Core.Application/Service/ReadingCalculate/LuganoWithoutPETCalculateService.cs +++ b/IRaCIS.Core.Application/Service/ReadingCalculate/LuganoWithoutPETCalculateService.cs @@ -26,9 +26,8 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate IRepository _tumorAssessmentRepository, ISubjectVisitService _subjectVisitService, IGeneralCalculateService _generalCalculateService, - IRepository _readingTaskQuestionAnswerRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, ICriterionCalculateService, ILuganoCalculateService + IRepository _readingTaskQuestionAnswerRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, ICriterionCalculateService, ILuganoWithoutPETCalculateService { - /// /// 阅片导入 /// @@ -352,7 +351,8 @@ namespace IRaCIS.Core.Application.Service.ReadingCalculate } - }; + } + ; } #endregion