From 874c8718b1f08425ed04ad148969469ad1ecc68b Mon Sep 17 00:00:00 2001 From: hang <87227557@qq.com> Date: Fri, 4 Oct 2024 17:54:55 +0800 Subject: [PATCH 1/5] TestMasstransitMeditor --- IRaCIS.Core.API/Progranm.cs | 14 ++-- .../IRaCIS.Core.Application.csproj | 1 - .../IRaCIS.Core.Application.xml | 5 ++ .../NeedVerify/AddSubjectTriggerConsumer.cs | 48 ++++++++++- ...ediatorHttpContextScopeFilterExtensions.cs | 83 +++++++++++++++++++ IRaCIS.Core.Application/TestService.cs | 12 ++- .../AddSubjectTriggerCommand.cs | 8 ++ .../Interceptor/AuditEntityInterceptor.cs | 9 +- .../DispatchDomainEventsInterceptor.cs | 13 ++- 9 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 IRaCIS.Core.Application/MassTransit/Extension/MediatorHttpContextScopeFilterExtensions.cs diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 808a5edec..5403af7fe 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -134,7 +134,7 @@ builder.Services.AddDynamicWebApiSetup(); //AutoMapper builder.Services.AddAutoMapperSetup(); //EF ORM QueryWithNoLock -builder.Services.AddEFSetup(_configuration,enviromentName); +builder.Services.AddEFSetup(_configuration, enviromentName); //Http 响应压缩 builder.Services.AddResponseCompressionSetup(); //Swagger Api 文档 @@ -151,15 +151,17 @@ builder.Services.AddJWTAuthSetup(_configuration); builder.Services.AddMediator(cfg => { cfg.AddConsumer(); - cfg.AddConsumer(); cfg.AddConsumer(); + cfg.AddConsumer(); + //cfg.ConfigureMediator((context, cfg) => cfg.UseHttpContextScopeFilter(context)); }); -// 添加 MassTransit 和 InMemory 传输 +//添加 MassTransit 和 InMemory 传输 builder.Services.AddMassTransit(cfg => { // 注册消费者 - //cfg.AddConsumer(); // 替换为你的消费者类 + cfg.AddConsumer(); // 替换为你的消费者类 + cfg.AddConsumer(); // 使用 InMemory 作为消息传递机制 cfg.UsingInMemory((context, cfg) => @@ -167,9 +169,8 @@ builder.Services.AddMassTransit(cfg => // 这里可以进行额外的配置 cfg.ConfigureEndpoints(context); // 自动配置所有消费者的端点 }); - - }); + #endregion @@ -211,6 +212,7 @@ builder.Services.AddSingleton(new Searcher(CachePolicy.Content, Path. //builder.Services.AddExceptionHandler(); //builder.Services.AddProblemDetails(); + #region 历史废弃配置 //builder.Services.AddMemoryCache(); ////上传限制 配置 diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj index d495031f6..3a509ace3 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj @@ -71,7 +71,6 @@ - diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 0a897fe0a..6132acfb8 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -12676,6 +12676,11 @@ + + + 参考链接:https://github.com/MassTransit/MassTransit/discussions/2498 + + TaskAllocationRuleView 列表视图模型 diff --git a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs b/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs index 67779cc06..f9875948e 100644 --- a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs +++ b/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs @@ -3,10 +3,15 @@ using AutoMapper; using IRaCIS.Core.Domain; using MassTransit; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace IRaCIS.Core.Application.MassTransit.Consumer; + + + /// /// 添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致 /// 因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务 @@ -25,7 +30,7 @@ public class AddSubjectTriggerConsumer(IRepository _subjectVisitRe { var addSubjectEvent = context.Message; - + { Console.WriteLine(_visitStageRepository._dbContext.GetHashCode()); @@ -53,3 +58,44 @@ public class AddSubjectTriggerConsumer(IRepository _subjectVisitRe await _subjectVisitRepository.AddRangeAsync(svList); } } + +public class AddSubjectTriggerConsumer2(IRepository _subjectVisitRepository, + + IRepository _visitStageRepository, + IRepository _trialRepository, + IMapper _mapper) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + var addSubjectEvent = context.Message; + + + { + Console.WriteLine(_visitStageRepository._dbContext.GetHashCode()); + + Console.WriteLine("两个 DbContext 不是同一个实例"); + } + + + //添加受试者的时候,获取访视计划列表,添加到受试者访视表。 + var visitPlanList = await _visitStageRepository.Where(t => t.TrialId == addSubjectEvent.TrialId && t.IsConfirmed).ToListAsync(); + + var svList = _mapper.Map>(visitPlanList); + + var IsEnrollementQualificationConfirm = await _trialRepository.Where(t => t.Id == addSubjectEvent.TrialId).Select(u => u.IsEnrollementQualificationConfirm).FirstOrDefaultAsync(); + + svList.ForEach(t => + { + t.SubjectId = addSubjectEvent.SubjectId; + t.TrialId = addSubjectEvent.TrialId; + t.TrialSiteId = addSubjectEvent.TrialSiteId; + t.IsEnrollmentConfirm = t.IsBaseLine ? IsEnrollementQualificationConfirm : false; + t.Id = NewId.NextGuid(); + + }); + + await _subjectVisitRepository.AddRangeAsync(svList); + } +} + + diff --git a/IRaCIS.Core.Application/MassTransit/Extension/MediatorHttpContextScopeFilterExtensions.cs b/IRaCIS.Core.Application/MassTransit/Extension/MediatorHttpContextScopeFilterExtensions.cs new file mode 100644 index 000000000..7a1d2684b --- /dev/null +++ b/IRaCIS.Core.Application/MassTransit/Extension/MediatorHttpContextScopeFilterExtensions.cs @@ -0,0 +1,83 @@ + + +using IRaCIS.Core.Application.MassTransit.Consumer; +using MassTransit; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace IRaCIS.Core.Application.MassTransit.Consumer; + +/// +/// 参考链接:https://github.com/MassTransit/MassTransit/discussions/2498 +/// +public static class MediatorHttpContextScopeFilterExtensions +{ + public static void UseHttpContextScopeFilter(this IMediatorConfigurator configurator, IServiceProvider serviceProvider) + { + var filter = new HttpContextScopeFilter(serviceProvider.GetRequiredService()); + + configurator.ConfigurePublish(x => x.UseFilter(filter)); + configurator.ConfigureSend(x => x.UseFilter(filter)); + configurator.UseFilter(filter); + } +} + +public class HttpContextScopeFilter : + IFilter, + IFilter, + IFilter +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextScopeFilter(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + private void AddPayload(PipeContext context) + { + if (_httpContextAccessor.HttpContext == null) + return; + + var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; + context.GetOrAddPayload(() => serviceProvider); + context.GetOrAddPayload(() => new NoopScope(serviceProvider)); + } + + public Task Send(PublishContext context, IPipe next) + { + AddPayload(context); + return next.Send(context); + } + + public Task Send(SendContext context, IPipe next) + { + AddPayload(context); + return next.Send(context); + } + + public Task Send(ConsumeContext context, IPipe next) + { + AddPayload(context); + return next.Send(context); + } + + public void Probe(ProbeContext context) + { + } + + private class NoopScope : + IServiceScope + { + public NoopScope(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public void Dispose() + { + } + + public IServiceProvider ServiceProvider { get; } + } +} \ No newline at end of file diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 998e3b66c..6127e3522 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -16,6 +16,7 @@ using Medallion.Threading; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MiniExcelLibs; @@ -126,7 +127,11 @@ namespace IRaCIS.Core.Application.Service } //My project is a monolithic project,And the efcore context repository is scoped registered. - public async Task TestMasstransitMeditor([FromServices] IScopedMediator _mediator, [FromServices] IRepository _testLengthRepository) + public async Task TestMasstransitMeditor( + [FromServices] IScopedMediator _mediatorScoped, + [FromServices] IMediator _mediator, + [FromServices] IRepository _testLengthRepository + ) { var dbContext = _testLengthRepository._dbContext; @@ -143,12 +148,13 @@ namespace IRaCIS.Core.Application.Service } - // add 1 recored await _testLengthRepository.AddAsync(new TestLength() { Name = "xxxx" }); // The consumer method will inject the repository and add 3 pieces of data, but the savechanges method of the repository will not be called - await _mediator.Send(new AddSubjectTriggerCommand { SubjectId = Guid.Empty }); + await _mediatorScoped.Send(new AddSubjectTriggerCommand { SubjectId = Guid.Empty }); + + await _mediator.Send(new AddSubjectTriggerCommand2 { SubjectId = Guid.Empty }); // this will save 1 record not 4 record ,Why is the dbcontext different? Can it be in the same transaction? await _testLengthRepository.SaveChangesAsync(); diff --git a/IRaCIS.Core.Domain/_DomainCommand/AddSubjectTriggerCommand.cs b/IRaCIS.Core.Domain/_DomainCommand/AddSubjectTriggerCommand.cs index 6e4a2cf73..96545c03d 100644 --- a/IRaCIS.Core.Domain/_DomainCommand/AddSubjectTriggerCommand.cs +++ b/IRaCIS.Core.Domain/_DomainCommand/AddSubjectTriggerCommand.cs @@ -14,3 +14,11 @@ public class AddSubjectTriggerCommand : DomainCommand public Guid TrialSiteId { get; set; } } + +public class AddSubjectTriggerCommand2 +{ + public Guid SubjectId { get; set; } + public Guid TrialId { get; set; } + + public Guid TrialSiteId { get; set; } +} diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs index a97b5a65d..32a2f49d9 100644 --- a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs @@ -11,7 +11,12 @@ using System.Data; namespace IRaCIS.Core.Infra.EFCore; - +/// +/// ef 官方文档 :https://learn.microsoft.com/zh-cn/ef/core/logging-events-diagnostics/interceptors +/// +/// +/// +/// public class AuditEntityInterceptor(IUserInfo _userInfo, ILogger _logger , IMediator _mediator @@ -28,7 +33,7 @@ public class AuditEntityInterceptor(IUserInfo _userInfo, public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { - //////领域命令 (同一个事务提交的一些逻辑,类似Trigger 保存事务之前执行的一些逻辑) + //////领域命令 (同一个事务提交的一些逻辑,类似Trigger 保存事务之前执行的一些逻辑) IMediator 和autofac 有冲突,不在一个事务,废弃。 //eventData.Context.AddDomainCommands(); //await DispatchDomainCommands(eventData.Context); diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs index 195d1c710..b854a0dc3 100644 --- a/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs @@ -1,14 +1,19 @@ using IRaCIS.Core.Domain; using IRaCIS.Core.Domain.Models; using MassTransit; +using MassTransit.Mediator; using Microsoft.EntityFrameworkCore.Diagnostics; using System.Data; namespace IRaCIS.Core.Infra.EFCore.Interceptor { - // ISendEndpoint:提供了Send方法,用于发送命令。 - //IPublishEndpoint:提供了Publish方法,用于发布事件。 - public class DispatchDomainEventsInterceptor(IPublishEndpoint publishEndpoint) : SaveChangesInterceptor + // 命令(Command):是一种定向的消息,应该由一个特定的接收者处理。用 Send 发送。 + // 事件(Event):是一种广播式消息,可以有多个订阅者接收并处理。用 Publish 发布。 + // IPublishEndpoint:提供了Publish方法,用于发布事件,该接口仅负责发布消息,不会依赖于接收者的存在,发布者和消费者可以是完全分离的 + // IMediator 适用于单进程内的消息传递,不涉及外部消息中间件(如 RabbitMQ、Azure Service Bus 等) 使用本地的内存队列,而不是跨进程消息传递,适合轻量级、无中间件的场景 + // 发件箱模式参考: https://dev.to/antonmartyniuk/use-masstransit-to-implement-outbox-pattern-with-ef-core-and-mongodb-oep#:~:text=MongoDB%20replica%20set%20is%20required%20for%20both%20publisher%20and%20consumer + // 1、IPublishEndpoint 才会将事件存储到发件箱表中, 高级IBus接口时 - 消息不会存储在发件箱中,必须有savechanges 才会一起提交保存到数据库中 + public class DispatchDomainEventsInterceptor(IMediator _mediator) : SaveChangesInterceptor { //领域事件通常与数据变更密切相关。如果在 SaveChanges 之前发布事件,有可能事件发布时的数据状态还没有被持久化到数据库。这可能导致事件消费者看到的是一个不一致的状态 @@ -52,7 +57,7 @@ namespace IRaCIS.Core.Infra.EFCore.Interceptor //这种方式会导致消息没处理 //await publishEndpoint.Publish(domainEvent); - await publishEndpoint.Publish(domainEvent.GetType(), domainEvent); + await _mediator.Publish(domainEvent.GetType(), domainEvent); } } From fac077d2b7cfee9f2a612c631dbbe5eedf654350 Mon Sep 17 00:00:00 2001 From: hang <87227557@qq.com> Date: Mon, 7 Oct 2024 21:27:44 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A4=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interceptor/AuditEntityInterceptor.cs | 10 ++++++++++ .../Interceptor/DispatchDomainEventsInterceptor.cs | 1 + 2 files changed, 11 insertions(+) diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs index 32a2f49d9..bb37a9a52 100644 --- a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs @@ -1,4 +1,5 @@ using EntityFramework.Exceptions.Common; +using IRaCIS.Core.Domain.BaseModel; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infra.EFCore.Common; @@ -36,6 +37,15 @@ public class AuditEntityInterceptor(IUserInfo _userInfo, //////领域命令 (同一个事务提交的一些逻辑,类似Trigger 保存事务之前执行的一些逻辑) IMediator 和autofac 有冲突,不在一个事务,废弃。 //eventData.Context.AddDomainCommands(); //await DispatchDomainCommands(eventData.Context); + // var domainEvent = JsonSerializer.Deserialize(storedEvent.EventData, Type.GetType(storedEvent.EventType)); + //// 序列化事件 + //var eventData = JsonSerializer.Serialize(domainEvent); + + //var storedEvent = new StoredEvent + //{ + // EventType = domainEvent.GetType().Name, + // EventData = eventData + //}; //领域事件 eventData.Context.AddDomainEvents(); diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs index b854a0dc3..171a9eeda 100644 --- a/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs @@ -13,6 +13,7 @@ namespace IRaCIS.Core.Infra.EFCore.Interceptor // IMediator 适用于单进程内的消息传递,不涉及外部消息中间件(如 RabbitMQ、Azure Service Bus 等) 使用本地的内存队列,而不是跨进程消息传递,适合轻量级、无中间件的场景 // 发件箱模式参考: https://dev.to/antonmartyniuk/use-masstransit-to-implement-outbox-pattern-with-ef-core-and-mongodb-oep#:~:text=MongoDB%20replica%20set%20is%20required%20for%20both%20publisher%20and%20consumer // 1、IPublishEndpoint 才会将事件存储到发件箱表中, 高级IBus接口时 - 消息不会存储在发件箱中,必须有savechanges 才会一起提交保存到数据库中 + // 2、进入消息代理之前,发布事件在OutboxState OutboxMessage, 进入消费者以后(已经删除OutboxState OutboxMessage),消费失败,需要修改代码重新发布,然后之前消费事件的重新处理,错误处理参考:https://www.youtube.com/watch?v=3TMKUu7c4lc public class DispatchDomainEventsInterceptor(IMediator _mediator) : SaveChangesInterceptor { From f539496f9f6044b4f474b299cf6b28a43254e324 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 8 Oct 2024 08:55:24 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=88=87=E6=8D=A2nuget=E5=8C=85=E6=89=80?= =?UTF-8?q?=E5=9C=A8=20=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj | 2 ++ IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj index b68ba65dc..aac993e26 100644 --- a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj +++ b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj @@ -25,6 +25,8 @@ + + diff --git a/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj b/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj index e5a59bc58..a59275bf9 100644 --- a/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj +++ b/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj @@ -11,7 +11,6 @@ - From 5bce1cafa392dea4f56d4bb6988eabb99b429d24 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 8 Oct 2024 10:46:10 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9lili=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRC.Core.SCP/appsettings.US_Prod_SCP.json | 4 ++-- IRaCIS.Core.API/appsettings.US_Prod_IRC.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/IRC.Core.SCP/appsettings.US_Prod_SCP.json b/IRC.Core.SCP/appsettings.US_Prod_SCP.json index e56626eb7..885e1ed84 100644 --- a/IRC.Core.SCP/appsettings.US_Prod_SCP.json +++ b/IRC.Core.SCP/appsettings.US_Prod_SCP.json @@ -15,8 +15,8 @@ "RoleArn": "arn:aws:iam::471112624751:role/lili_s3_access", "AccessKeyId": "AKIAW3MEAFJXZ2TZK7GM", "SecretAccessKey": "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", - "BucketName": "ei-med-s3-lili-uat-store", - "ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com/", + "BucketName": "ei-med-s3-lili-store", + "ViewEndpoint": "https://ei-med-s3-lili-store.s3.amazonaws.com", "DurationSeconds": 7200 } }, diff --git a/IRaCIS.Core.API/appsettings.US_Prod_IRC.json b/IRaCIS.Core.API/appsettings.US_Prod_IRC.json index b08ad4fe6..e64ab36a0 100644 --- a/IRaCIS.Core.API/appsettings.US_Prod_IRC.json +++ b/IRaCIS.Core.API/appsettings.US_Prod_IRC.json @@ -32,8 +32,8 @@ "RoleArn": "arn:aws:iam::471112624751:role/lili_s3_access", "AccessKeyId": "AKIAW3MEAFJXZ2TZK7GM", "SecretAccessKey": "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", - "BucketName": "ei-med-s3-lili-uat-store", - "ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com", + "BucketName": "ei-med-s3-lili-store", + "ViewEndpoint": "https://ei-med-s3-lili-store.s3.amazonaws.com", "DurationSeconds": 7200 } }, From 658f9c7c404d003b1acef48f27eb02bb0d30cd1c Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 8 Oct 2024 11:38:37 +0800 Subject: [PATCH 5/5] =?UTF-8?q?1=E3=80=81=E9=98=85=E7=89=87=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA=E8=A1=A8=202=E3=80=81=E6=8E=A5=E6=94=B6=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E8=A1=A8=203=E3=80=81=E6=8E=A5=E6=94=B6=E5=BD=B1?= =?UTF-8?q?=E5=83=8F=E6=A3=80=E6=9F=A5=E8=A1=A8=204=E3=80=81=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=AE=B0=E5=BD=95=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IRaCIS.Core.Application.xml | 24 +++ .../Service/Common/ExcelExportService.cs | 139 ++++++++++++++++++ .../Service/Common/_MapConfig.cs | 7 + .../Service/QC/DTO/QCListViewModel.cs | 89 +++++++++++ .../Service/Visit/DTO/PatientViewModel.cs | 36 +++++ .../Service/Visit/_MapConfig.cs | 10 +- .../_IRaCIS/_Config/_StaticData.cs | 6 +- 7 files changed, 307 insertions(+), 4 deletions(-) diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 6132acfb8..dd706d5f7 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -712,6 +712,30 @@ + + + 影像下载记录表 + + + + + + + + + + 影像接收记录表 + + + + + + + 影像检查列表-患者为维度组织 + + + + 自身一致性分析(仅做了resist1.1) diff --git a/IRaCIS.Core.Application/Service/Common/ExcelExportService.cs b/IRaCIS.Core.Application/Service/Common/ExcelExportService.cs index 91e2206a8..b1fc7c25b 100644 --- a/IRaCIS.Core.Application/Service/Common/ExcelExportService.cs +++ b/IRaCIS.Core.Application/Service/Common/ExcelExportService.cs @@ -1204,6 +1204,145 @@ namespace IRaCIS.Core.Application.Service.Common } + /// + /// 影像下载记录表 + /// + /// + /// + /// + /// + /// + [HttpPost] + public async Task GetTrialDownloadList_Export(TrialIamgeDownQuery inQuery, + [FromServices] IRepository _trialImageDownloadRepository, + [FromServices] IDictionaryService _dictionaryService, + [FromServices] IRepository _trialRepository) + { + var query = _trialImageDownloadRepository.Where(t => t.TrialId == inQuery.TrialId) + .WhereIf(inQuery.SubjectCode.IsNotNullOrEmpty(), t => t.SubjectCode.Contains(inQuery.SubjectCode)) + .WhereIf(inQuery.IP.IsNotNullOrEmpty(), t => t.IP.Contains(inQuery.IP)) + .WhereIf(inQuery.Name.IsNotNullOrEmpty(), t => t.CreateUser.UserName.Contains(inQuery.Name) || t.CreateUser.FullName.Contains(inQuery.Name)) + .WhereIf(inQuery.ImageType != null, t => t.ImageType == inQuery.ImageType) + .WhereIf(inQuery.UserType != null, t => t.CreateUser.UserTypeEnum == inQuery.UserType) + .WhereIf(inQuery.IsSuccess != null, t => t.IsSuccess == inQuery.IsSuccess) + .WhereIf(inQuery.DownloadStartTime != null, t => t.DownloadStartTime >= inQuery.DownloadStartTime) + .WhereIf(inQuery.DownloadEndTime != null, t => t.DownloadEndTime <= inQuery.DownloadEndTime) + + .ProjectTo(_mapper.ConfigurationProvider); + + var list = query.SortToListAsync(inQuery); + + + var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException(); + + exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId); + exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId); + + + return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialImageDownloadList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(TrialImageDownloadExportDto)); + + } + + /// + /// 影像接收记录表 + /// + /// + /// + [HttpPost] + public async Task GetSCPImageUploadList_Export(SCPImageUploadQuery inQuery, + [FromServices] IRepository _scpImageUploadRepository, + [FromServices] IDictionaryService _dictionaryService, + [FromServices] IRepository _trialRepository) + { + var query = _scpImageUploadRepository.Where(t => t.TrialId == inQuery.TrialId) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.CalledAE.Contains(inQuery.CalledAE)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAEIP), t => t.CallingAEIP.Contains(inQuery.CallingAEIP)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.CallingAE.Contains(inQuery.CallingAE)) + .WhereIf(inQuery.StartTime != null, t => t.StartTime >= inQuery.StartTime) + .WhereIf(inQuery.EndTime != null, t => t.EndTime <= inQuery.EndTime) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo) + || t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo)) + .ProjectTo(_mapper.ConfigurationProvider); + + + var list = query.SortToListAsync(inQuery); + + var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException(); + + exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId); + exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId); + + + return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialSCPImageUploadList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(SCPImageUploadExportDTO)); + + } + + /// + ///影像检查列表-患者为维度组织 + /// + /// + /// + [HttpPost] + public async Task GetPatientList_Export(PatientTrialQuery inQuery, + [FromServices] IRepository _patientRepository, + [FromServices] IDictionaryService _dictionaryService, + [FromServices] IRepository _trialRepository) + { + + + var query = _patientRepository.Where(t => t.TrialId == inQuery.TrialId) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientIdStr), t => t.PatientIdStr.Contains(inQuery.PatientIdStr)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientName), t => t.PatientName.Contains(inQuery.PatientName)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.SubejctCode), t => t.Subject.Code.Contains(inQuery.SubejctCode)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo) + || t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.SCPStudyList.Any(t => t.CallingAE == inQuery.CallingAE)) + .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.SCPStudyList.Any(t => t.CalledAE == inQuery.CalledAE)) + .WhereIf(inQuery.BeginPushTime != null, t => t.LatestPushTime >= inQuery.BeginPushTime) + .WhereIf(inQuery.EndPushTime != null, t => t.LatestPushTime <= inQuery.EndPushTime); + + + var resultQuery = from patient in query + + select new SCPPatientSubjectExportDTO() + { + //CreateUserId = patient.CreateUserId, + //UpdateTime = patient.UpdateTime, + //UpdateUserId = patient.UpdateUserId, + //TrialId = patient.TrialId, + //SubejctId = patient.SubjectId, + //CreateTime = patient.CreateTime, + //PatientId = patient.Id, + + PatientBirthDate = patient.PatientBirthDate, + CalledAEList = patient.SCPStudyList.Select(t => t.CalledAE).Distinct().ToList(), + CallingAEList = patient.SCPStudyList.Select(t => t.CallingAE).Distinct().ToList(), + EarliestStudyTime = patient.EarliestStudyTime, + LatestStudyTime = patient.LatestStudyTime, + LatestPushTime = patient.LatestPushTime, + PatientAge = patient.PatientAge, + PatientName = patient.PatientName, + PatientIdStr = patient.PatientIdStr, + PatientSex = patient.PatientSex, + StudyCount = patient.SCPStudyList.Count(), + SubjectCode = patient.Subject.Code, + TrialSiteAliasName = patient.TrialSite.TrialSiteAliasName, + TrialSiteCode = patient.TrialSite.TrialSiteCode, + TrialSiteName = patient.TrialSite.TrialSiteName + + }; + + var list = query.SortToListAsync(inQuery); + + var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException(); + + exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId); + exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId); + + + return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialSCPImageUploadPatientList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(SCPPatientSubjectExportDTO)); + + } /// /// 自身一致性分析(仅做了resist1.1) diff --git a/IRaCIS.Core.Application/Service/Common/_MapConfig.cs b/IRaCIS.Core.Application/Service/Common/_MapConfig.cs index 932be43f8..aa3410c8d 100644 --- a/IRaCIS.Core.Application/Service/Common/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/Common/_MapConfig.cs @@ -82,6 +82,13 @@ namespace IRaCIS.Core.Application.Service CreateMap(); CreateMap().ReverseMap(); + + CreateMap() + .ForMember(d => d.UserFullName, u => u.MapFrom(s => s.CreateUser.FullName)) + .ForMember(d => d.UserTypeEnum, u => u.MapFrom(s => s.CreateUser.UserTypeEnum)) + .ForMember(d => d.UserName, u => u.MapFrom(s => s.CreateUser.UserName)); + + } } diff --git a/IRaCIS.Core.Application/Service/QC/DTO/QCListViewModel.cs b/IRaCIS.Core.Application/Service/QC/DTO/QCListViewModel.cs index c925ecfca..fca4c712c 100644 --- a/IRaCIS.Core.Application/Service/QC/DTO/QCListViewModel.cs +++ b/IRaCIS.Core.Application/Service/QC/DTO/QCListViewModel.cs @@ -871,6 +871,95 @@ namespace IRaCIS.Core.Application.Contracts public bool IsInvalid { get; set; } } + public class TrialImageDownloadExportDto + { + public Guid TrialId { get; set; } + + public string SubjectCode { get; set; } + + public bool IsSuccess { get; set; } + + public DateTime DownloadStartTime { get; set; } + public DateTime? DownloadEndTime { get; set; } + + public string VisitName { get; set; } + + [DictionaryTranslateAttribute("downloadImageType")] + public ImageType ImageType { get; set; } + + public int NoneDicomStudyCount { get; set; } + + public int DicomStudyCount { get; set; } + + public int ImageCount { get; set; } + public long ImageSize { get; set; } + + + public string UserName { get; set; } + + public string UserFullName { get; set; } + public DateTime CreateTime { get; set; } + + public string IP { get; set; } + + [DictionaryTranslateAttribute("UserType")] + public UserTypeEnum UserTypeEnum { get; set; } + + public string ImageSizeStr => (ImageSize / Math.Pow(1024, 2)).ToString("F3") + 'M'; + + public string UploadIntervalStr + { + get + { + var uploadTimeSpan = DownloadEndTime - DownloadStartTime; + + return $" {uploadTimeSpan?.Hours}:{uploadTimeSpan?.Minutes}:{uploadTimeSpan?.Seconds}.{uploadTimeSpan?.Milliseconds}"; + } + } + + } + + public class SCPImageUploadExportDTO + { + public string CallingAE { get; set; } = string.Empty; + + public string CalledAE { get; set; } = string.Empty; + + public string CallingAEIP { get; set; } = string.Empty; + + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public int FileCount { get; set; } + + public long FileSize { get; set; } + + public int StudyCount { get; set; } + + + public Guid TrialId { get; set; } + public Guid TrialSiteId { get; set; } + + + public string TrialSiteCode { get; set; } + + public string TrialSiteName { get; set; } + + public string TrialSiteAliasName { get; set; } + + public string UploadIntervalStr + { + get + { + var uploadTimeSpan = EndTime - StartTime; + + return $" {uploadTimeSpan.Hours}:{uploadTimeSpan.Minutes}:{uploadTimeSpan.Seconds}.{uploadTimeSpan.Milliseconds}"; + } + } + + public string ImageSizeStr => (FileSize / Math.Pow(1024, 2)).ToString("F3") + 'M'; + } public class AnalysisExortCommon { diff --git a/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs b/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs index f7477c78a..8b8fa455f 100644 --- a/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs +++ b/IRaCIS.Core.Application/Service/Visit/DTO/PatientViewModel.cs @@ -256,9 +256,45 @@ namespace IRaCIS.Application.Contracts public string? TrialSiteName { get; set; } public string? TrialSiteAliasName { get; set; } + + + } + public class SCPPatientSubjectExportDTO + { + public int? StudyCount { get; set; } + + public string? SubjectCode { get; set; } + + public string? TrialSiteCode { get; set; } + + public string? TrialSiteName { get; set; } + + public string? TrialSiteAliasName { get; set; } + + public string PatientIdStr { get; set; } = string.Empty; + public string PatientName { get; set; } = string.Empty; + public string PatientAge { get; set; } = string.Empty; + public string PatientSex { get; set; } = string.Empty; + public string PatientBirthDate { get; set; } = string.Empty; + + public DateTime? EarliestStudyTime { get; set; } + + public DateTime? LatestStudyTime { get; set; } + + public DateTime LatestPushTime { get; set; } + + public List CallingAEList { get; set; } = new List(); + + public List CalledAEList { get; set; } = new List(); + + public string CallingAEListStr => string.Join(",", CallingAEList); + + public string CalledAEListStr => string.Join(",", CalledAEList); + } + public class PatientQuery : PageInput { diff --git a/IRaCIS.Core.Application/Service/Visit/_MapConfig.cs b/IRaCIS.Core.Application/Service/Visit/_MapConfig.cs index 7ffd90487..cf927ee17 100644 --- a/IRaCIS.Core.Application/Service/Visit/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/Visit/_MapConfig.cs @@ -112,9 +112,13 @@ namespace IRaCIS.Core.Application.Service CreateMap() .ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.TrialSite.TrialSiteCode)) - .ForMember(d => d.TrialSiteAliasName, u => u.MapFrom(s => s.TrialSite.TrialSiteAliasName)) - .ForMember(d => d.TrialSiteName, u => u.MapFrom(s => s.TrialSite.TrialSiteName)) -; + .ForMember(d => d.TrialSiteAliasName, u => u.MapFrom(s => s.TrialSite.TrialSiteAliasName)) + .ForMember(d => d.TrialSiteName, u => u.MapFrom(s => s.TrialSite.TrialSiteName)); + + CreateMap() + .ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.TrialSite.TrialSiteCode)) + .ForMember(d => d.TrialSiteAliasName, u => u.MapFrom(s => s.TrialSite.TrialSiteAliasName)) + .ForMember(d => d.TrialSiteName, u => u.MapFrom(s => s.TrialSite.TrialSiteName)); CreateMap(); diff --git a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs index 0d06b9e5c..cd320ed98 100644 --- a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs +++ b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs @@ -252,7 +252,11 @@ public static class StaticData public const string TrialMedicalReviewList_Export = "TrialMedicalReviewList_Export"; - + public static string TrialImageDownloadList_Export = "TrialImageDownloadList_Export"; + + public static string TrialSCPImageUploadList_Export = "TrialSCPImageUploadList_Export"; + + public static string TrialSCPImageUploadPatientList_Export = "TrialSCPImageUploadPatientList_Export"; //public const string TrialRECIST1Point1SelfAnalysisList_Export = "TrialRECIST1Point1SelfAnalysisList_Export";