Compare commits

..

60 Commits

Author SHA1 Message Date
hang e4ad1c0653 增加失访可读配置-uat
continuous-integration/drone/push Build is passing
2025-06-25 15:31:59 +08:00
he 84df49ca07 单位修改2
continuous-integration/drone/push Build is passing
2025-06-24 14:58:39 +08:00
he 400edd0b70 修改单位1 2025-06-24 14:58:37 +08:00
he c4add1ecc8 修改单位 2025-06-24 14:58:35 +08:00
he d5879e28d7 修改OCT计算触发逻辑
continuous-integration/drone/push Build is passing
2025-06-24 14:07:31 +08:00
he 37e083face 阅片标准默认值修改 2025-06-24 14:07:28 +08:00
he 290506a060 保存测量值
continuous-integration/drone/push Build is passing
2025-06-24 09:28:27 +08:00
he 1b4bc3690a 稽查保存Ivus和Oct 2025-06-24 09:28:25 +08:00
he 6374781d26 修改阅片期计划的排序
continuous-integration/drone/push Build is failing
2025-06-20 17:33:23 +08:00
he b28013a02f 阅片期计划可以选择基线 2025-06-20 17:32:07 +08:00
he fe429d2bdc 修改受试者内随机
continuous-integration/drone/push Build is passing
2025-06-20 09:37:48 +08:00
he d63af2ed20 修改稽查的单位翻译
continuous-integration/drone/push Build is passing
2025-06-19 17:16:42 +08:00
hang cebf9875b1 禁用人员系统查看文档
continuous-integration/drone/push Build is passing
2025-06-19 10:31:24 +08:00
hang 131681a0b3 完全随机触发系统盲态名称 2025-06-19 10:22:06 +08:00
hang f833adc710 修改有序阅片生成任务bug 2025-06-19 10:21:59 +08:00
hang df80d1e551 修改影像下载bug
continuous-integration/drone/push Build is passing
2025-06-18 10:36:26 +08:00
he 82dc092247 修改阅片期计划生成裁判的逻辑
continuous-integration/drone/push Build is passing
2025-06-17 16:42:47 +08:00
he 44894fcaa9 生成阅片期计划的时候 产生裁判
continuous-integration/drone/push Build is passing
2025-06-17 16:24:00 +08:00
hang 083bebdc03 真实随机阅片导表-1
continuous-integration/drone/push Build is passing
2025-06-17 10:06:41 +08:00
hang e808764dd6 随机阅片排序-5 2025-06-17 10:06:33 +08:00
hang 77d198cb09 随机阅片-NextTask-5 2025-06-17 10:05:55 +08:00
hang 180b02b28c 随机阅片NextTask-4 2025-06-17 10:04:26 +08:00
hang c49a74d742 Merge branch 'Uat_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-06-17 10:02:34 +08:00
he c76fc8f052 修改阅片期选择访视下拉框的数据
continuous-integration/drone/push Build is running
2025-06-17 10:02:04 +08:00
he 14875dfacc 生成了裁判任务 阅片期不让删除和修改 2025-06-17 10:02:02 +08:00
hang 0a68788c0f 随机阅片序号3 2025-06-17 10:00:39 +08:00
hang b17e5d9ff3 随机阅片序号2 2025-06-17 10:00:34 +08:00
hang 05ce4b7155 随机阅片随机序号 2025-06-17 10:00:28 +08:00
he c8bbb3da48 Merge branch 'Uat_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-06-16 15:16:20 +08:00
he 65de108ecd 添加阅片期 产生裁判 2025-06-16 15:16:19 +08:00
hang d20c306219 末次访视的展示+ 展示基线选择阅片期
continuous-integration/drone/push Build is passing
2025-06-16 14:50:23 +08:00
hang e69991ae37 修改重置阅片稽查3
continuous-integration/drone/push Build is passing
2025-06-13 09:57:33 +08:00
hang e1e2544ee3 修改阅片重置稽查 2025-06-13 09:57:30 +08:00
hang 318333bae4 修改重置阅片稽查 2025-06-13 09:57:26 +08:00
hang d569de4dfe 确认浏览临床数据
continuous-integration/drone/push Build is passing
2025-06-12 10:52:46 +08:00
hang 05627ff126 肿瘤学稽查修改
continuous-integration/drone/push Build is passing
2025-06-11 10:03:01 +08:00
hang 627618c854 增加进入阅片中稽查 2025-06-11 10:02:57 +08:00
hang 2711c5eb04 稽查 申请PM 重阅,有SPM 判断 2025-06-11 10:00:57 +08:00
hang a6982f79c3 稽查修改,区分标识 2025-06-11 10:00:53 +08:00
hang 2a542949cd 增加稽查标识 2025-06-11 10:00:49 +08:00
he a8ccbce76f 稽查添加
continuous-integration/drone/push Build is passing
2025-06-05 16:46:06 +08:00
he df7cf30aed 测量值可以为空
continuous-integration/drone/push Build is passing
2025-06-04 14:25:57 +08:00
he 0664fbdfb7 验证修改
continuous-integration/drone/push Build is passing
2025-06-04 09:57:08 +08:00
he 0a6ee05417 Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-05-22 18:01:11 +08:00
hang f9bd8c3f5c Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-05-21 15:16:26 +08:00
he cb83c2a737 Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-05-21 14:05:30 +08:00
he fdd9afd4f0 Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-05-20 15:46:24 +08:00
he 53d26c0445 Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is passing
2025-05-20 10:55:36 +08:00
he 40d035e7a7 解决合并的冲突
continuous-integration/drone/push Build is passing
2025-05-15 09:33:17 +08:00
he a13807ae5f 合并冲突修改
continuous-integration/drone/push Build is failing
2025-05-15 09:29:41 +08:00
he 9d5aaf1e26 Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build is failing
2025-05-15 09:20:22 +08:00
he c347d08e2b 合并
continuous-integration/drone/push Build is failing
2025-05-15 09:14:10 +08:00
hang 87f2d0e429 非dicom 删除上传
continuous-integration/drone/push Build is passing
2025-04-15 13:27:33 +08:00
he 89d009fcbf Revert "修改"
continuous-integration/drone/push Build is passing
This reverts commit 7d65cf5051.
2025-04-09 11:00:45 +08:00
he 7d65cf5051 修改
continuous-integration/drone/push Build is passing
2025-04-09 09:45:49 +08:00
he e74427c45c 解决冲突
continuous-integration/drone/push Build is passing
2025-04-08 11:05:31 +08:00
he e8825c7efa 修改查询 2025-04-08 11:04:52 +08:00
hang 628e0ad034 修改异地登录bug 设置最后一次登录的iP
continuous-integration/drone/push Build is passing
2025-04-03 13:55:04 +08:00
hang 2f3f639918 修改异地登录ip逻辑 2025-04-03 13:55:00 +08:00
hang 87caf24dd1 验证环境生成缩略图测试ok提交
continuous-integration/drone/push Build is passing
2025-04-03 13:49:34 +08:00
622 changed files with 25431 additions and 2221741 deletions
@@ -1,37 +0,0 @@
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
namespace IRaCIS.Core.SCP.Filter
{
public class ModelActionFilter : ActionFilterAttribute, IActionFilter
{
public IStringLocalizer _localizer;
public ModelActionFilter(IStringLocalizer localizer)
{
_localizer = localizer;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k]!.Errors)
.Select(e => e.ErrorMessage)
.ToArray();
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject( validationErrors)));
}
}
}
}
@@ -1,61 +0,0 @@
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.SCP.Filter
{
public class ProjectExceptionFilter : Attribute, IExceptionFilter
{
private readonly ILogger<ProjectExceptionFilter> _logger;
public IStringLocalizer _localizer;
public ProjectExceptionFilter(IStringLocalizer localizer, ILogger<ProjectExceptionFilter> logger)
{
_logger = logger;
_localizer = localizer;
}
public void OnException(ExceptionContext context)
{
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
if (!context.ExceptionHandled)
{
if (context.Exception.GetType().Name == "DbUpdateConcurrencyException")
{
//---并发更新,当前不允许该操作
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + context.Exception.Message));
}
if (context.Exception.GetType() == typeof(BusinessValidationFailedException))
{
var error = context.Exception as BusinessValidationFailedException;
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, error!.Code));
}
else if(context.Exception.GetType() == typeof(QueryBusinessObjectNotExistException))
{
context.Result = new JsonResult(ResponseOutput.NotOk( context.Exception.Message, ApiResponseCodeEnum.DataNotExist));
}
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (context.Exception.InnerException is null ? (context.Exception.Message /*+ context.Exception.StackTrace*/)
: (context.Exception.InnerException?.Message /*+ context.Exception.InnerException?.StackTrace*/)), ApiResponseCodeEnum.ProgramException));
}
_logger.LogError(context.Exception.InnerException is null ? (context.Exception.Message + context.Exception.StackTrace) : (context.Exception.InnerException?.Message + context.Exception.InnerException?.StackTrace));
}
else
{
//继续
}
context.ExceptionHandled = true;//标记当前异常已经被处理过了
}
}
}
@@ -1,120 +0,0 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Core.Application.Service.BusinessFilter
{
/// <summary>
/// 统一返回前端数据包装,之前在控制器包装,现在修改为动态Api 在ResultFilter这里包装,减少重复冗余代码
/// by zhouhang 2021.09.12 周末
/// </summary>
public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
{
/// <summary>
/// 异步版本
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is ObjectResult objectResult)
{
var statusCode = objectResult.StatusCode ?? context.HttpContext.Response.StatusCode;
//是200 并且没有包装 那么包装结果
if (statusCode == 200 && !(objectResult.Value is IResponseOutput))
{
//if (objectResult.Value == null)
//{
// var apiResponse = ResponseOutput.DBNotExist();
// objectResult.Value = apiResponse;
// objectResult.DeclaredType = apiResponse.GetType();
//}
//else
//{
var type = objectResult.Value?.GetType();
if ( type!=null&& type.IsGenericType&&(type.GetGenericTypeDefinition()==typeof(ValueTuple<,>)|| type.GetGenericTypeDefinition()==typeof(Tuple<,>)))
{
//报错
//var tuple = (object, object))objectResult.Value;
//var (val1, val2) = ((dynamic, dynamic))objectResult.Value;
//var apiResponse = ResponseOutput.Ok(val1, val2);
//OK
var tuple = (dynamic)objectResult.Value;
var apiResponse = ResponseOutput.Ok(tuple.Item1, tuple.Item2);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
else
{
var apiResponse = ResponseOutput.Ok(objectResult.Value);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
//}
}
//如果不是200 是IResponseOutput 不处理
else if (statusCode != 200 && (objectResult.Value is IResponseOutput))
{
}
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
{
//---程序错误,请联系开发人员。
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
}
await next.Invoke();
}
public static bool IsTupleType(Type type, bool checkBaseTypes = false)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type == typeof(Tuple))
return true;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(Tuple<>)
|| genType == typeof(Tuple<,>)
|| genType == typeof(Tuple<,>))
return true;
}
if (!checkBaseTypes)
break;
type = type.BaseType;
}
return false;
}
}
}
@@ -1,57 +0,0 @@
using Autofac;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Http;
using Panda.DynamicWebApi;
using System;
using System.Linq;
using System.Reflection;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Service;
using AutoMapper;
using IRaCIS.Core.SCP.Service;
namespace IRaCIS.Core.SCP
{
// ReSharper disable once IdentifierTypo
public class AutofacModuleSetup : Autofac.Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
#region byzhouhang 20210917 Domain层 Infra.EFcore
containerBuilder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
#endregion
#region autofac https://www.cnblogs.com/xwhqwer/p/15320838.html
//获取所有控制器类型并使用属性注入
containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
.Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
.PropertiesAutowired();
#endregion
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + typeof(BaseService).Assembly.GetName().Name+".dll");
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
.PropertiesAutowired().AsImplementedInterfaces();
//containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
//containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
}
}
}
-64
View File
@@ -1,64 +0,0 @@
using EntityFramework.Exceptions.SqlServer;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using Medallion.Threading.SqlServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.SCP
{
public static class EFSetup
{
public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration)
{
services.AddHttpContextAccessor();
services.AddScoped<IUserInfo, UserInfo>();
services.AddScoped<ISaveChangesInterceptor, AuditEntityInterceptor>();
//这个注入没有成功--注入是没问题的,构造函数也只是支持参数就好,错在注入的地方不能写DbContext
//Web程序中通过重用池中DbContext实例可提高高并发场景下的吞吐量, 这在概念上类似于ADO.NET Provider原生的连接池操作方式,具有节省DbContext实例化成本的优点
services.AddDbContext<IRaCISDBContext>((sp, options) =>
{
// 在控制台
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value,
contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseLoggerFactory(logFactory);
options.UseExceptionProcessor();
options.EnableSensitiveDataLogging();
options.AddInterceptors(new QueryWithNoLockDbCommandInterceptor());
options.AddInterceptors(sp.GetServices<ISaveChangesInterceptor>());
options.UseProjectables();
});
//// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
//services.AddScoped<IRaCISDBScopedFactory>();
//// Finally, arrange for a context to get injected from our Scoped factory:
//services.AddScoped(sp => sp.GetRequiredService<IRaCISDBScopedFactory>().CreateDbContext());
//注意区分 easy caching 也有 IDistributedLockProvider
services.AddSingleton<IDistributedLockProvider>(sp =>
{
//var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
return new SqlDistributedSynchronizationProvider(configuration.GetSection("ConnectionStrings:RemoteNew").Value);
});
}
}
}
@@ -1,59 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace IRaCIS.Core.SCP
{
public static class NewtonsoftJsonSetup
{
public static void AddNewtonsoftJsonSetup(this IMvcBuilder builder, IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IOSSService,OSSService>();
builder.AddNewtonsoftJson(options =>
{
//options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// 忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
//处理返回给前端 可空类型 给出默认值 比如in? 为null 设置 默认值0
options.SerializerSettings.ContractResolver = new NullToEmptyStringResolver(); //new DefaultContractResolver();// new NullToEmptyStringResolver();
// 设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
//options.SerializerSettings.Converters.Add(new JSONCustomDateConverter()) ;
//options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
})
.AddControllersAsServices()//动态webApi属性注入需要
.ConfigureApiBehaviorOptions(o =>
{
o.SuppressModelStateInvalidFilter = true; //自己写验证
});
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
{
//日期类型默认格式化处理
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return setting;
});
}
}
}
@@ -1,36 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
namespace IRaCIS.Core.SCP
{
public class NullToEmptyStringResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
var list= type.GetProperties()
.Select(p =>
{
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new NullToEmptyStringValueProvider(p);
return jp;
}).ToList();
var uu = list.Select(t => t.PropertyName).ToList();
//获取复杂对象属性
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
list.AddRange(properties);
return list;
}
}
}
@@ -1,42 +0,0 @@
using System;
using System.Reflection;
using Newtonsoft.Json.Serialization;
namespace IRaCIS.Core.SCP
{
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
return result;
}
public void SetValue(object target, object value)
{
if(_MemberInfo.PropertyType == typeof(string))
{
//去掉前后空格
_MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value.ToString().Trim());
}
else
{
_MemberInfo.SetValue(target, value);
}
}
}
}
-46
View File
@@ -1,46 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj" />
<ProjectReference Include="..\IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Helper\" />
</ItemGroup>
</Project>
-186
View File
@@ -1,186 +0,0 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.Imaging.NativeCodec;
using FellowOakDicom.Network;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.SCP;
using IRaCIS.Core.SCP.Filter;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using MassTransit.NewIdProviders;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Panda.DynamicWebApi;
using Serilog;
using Serilog.Events;
using System.Runtime.InteropServices;
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = enviromentName
});
#region
NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
builder.Configuration.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{enviromentName}.json", false, true);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<AutofacModuleSetup>();
})
.UseSerilog();
#endregion
#region
var _configuration = builder.Configuration;
//健康检查
builder.Services.AddHealthChecks();
//本地化
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
builder.Services.AddOptions().Configure<DicomSCPServiceOption>(_configuration.GetSection("DicomSCPServiceConfig"));
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
//动态webApi 目前存在的唯一小坑是生成api上服务上的动态代理AOP失效 间接掉用不影响
builder.Services
.AddDynamicWebApi(dynamicWebApiOption =>
{
//默认是 api
dynamicWebApiOption.DefaultApiPrefix = "";
//首字母小写
dynamicWebApiOption.GetRestFulActionName = (actionName) => char.ToLower(actionName[0]) + actionName.Substring(1);
//删除 Service后缀
dynamicWebApiOption.RemoveControllerPostfixes.Add("Service");
});
//AutoMapper
builder.Services.AddAutoMapper(automapper =>
{
automapper.AddCollectionMappers();
}, typeof(BaseService).Assembly);
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
builder.Services.AddMediator(cfg =>
{
});
//转发头设置 获取真实IP
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>();
#endregion
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.UseAuthorization();
app.MapControllers();
#region
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
#endregion
#region
Log.Logger.Warning($"当前环境:{enviromentName}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Log.Logger.Warning($"当前部署平台环境:windows");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Log.Logger.Warning($"当前部署平台环境:linux");
}
else
{
Log.Logger.Warning($"当前部署平台环境:OSX or FreeBSD");
}
#endregion
DicomSetupBuilder.UseServiceProvider(app.Services);
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services, logger: logger);
app.Run();
@@ -1,31 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:11224",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5127",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
-111
View File
@@ -1,111 +0,0 @@
using AutoMapper;
using IRaCIS.Core.Application.Service.BusinessFilter;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Localization;
using Panda.DynamicWebApi;
using Panda.DynamicWebApi.Attributes;
using System.Diagnostics.CodeAnalysis;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.Domain.Models;
namespace IRaCIS.Core.SCP.Service
{
#pragma warning disable CS8618
#region
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseService : IBaseService, IDynamicWebApi
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public IWebHostEnvironment _hostEnvironment { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
public interface IBaseService
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
[MemberNotNull(nameof(_hostEnvironment))]
public IWebHostEnvironment _hostEnvironment { get; set; }
}
#endregion
#region
public interface IBaseServiceTest<T> where T : Entity
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
}
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
#endregion
}
-376
View File
@@ -1,376 +0,0 @@
using FellowOakDicom.Network;
using FellowOakDicom;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using IRaCIS.Core.Domain.Share;
using Serilog;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Options;
using System.Data;
using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Core.SCP.Service
{
public class DicomSCPServiceOption
{
public List<string> CalledAEList { get; set; }
public string ServerPort { get; set; }
}
public class CStoreSCPService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
private IServiceProvider _serviceProvider { get; set; }
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
private SCPImageUpload _upload { get; set; }
private Guid _trialId { get; set; }
private Guid _trialSiteId { get; set; }
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
{
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
{
// Lossless
DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80
DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90
DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70
DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14)
DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5
// Lossy
DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81"
DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91
DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50
DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51
// Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1
DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2
DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2
};
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
: base(stream, fallbackEncoding, log, dependencies)
{
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
}
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
{
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
//_serviceProvider = (IServiceProvider)this.UserState;
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList();
var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList();
Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList));
var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault();
var isCanReceiveIamge = false;
if (findCalledAE != null)
{
_trialId = findCalledAE.TrialId;
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
_trialSiteId = findTrialSiteAE.TrialSiteId;
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
{
isCanReceiveIamge = true;
}
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false)
{
Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接");
return SendAssociationRejectAsync(
DicomRejectResult.Permanent,
DicomRejectSource.ServiceUser,
DicomRejectReason.CalledAENotRecognized);
}
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification)
{
pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);
}
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
{
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
}
}
return SendAssociationAcceptAsync(association);
}
public async Task OnReceiveAssociationReleaseRequestAsync()
{
await DataMaintenanceAsaync();
//记录监控
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
await _SCPImageUploadRepository.AddAsync(_upload, true);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
private async Task DataMaintenanceAsaync()
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束:开始维护数据,处理检查Modality");
//处理检查Modality
var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList();
var seriesModalityList = _seriesRepository.Where(t => _SCPStudyIdList.Contains(t.StudyId)).Select(t => new { SCPStudyId = t.StudyId, t.Modality }).ToList();
foreach (var g in seriesModalityList.GroupBy(t => t.SCPStudyId))
{
var modality = string.Join('、', g.Select(t => t.Modality).Distinct().ToList());
//特殊逻辑
var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty;
if (modality == "MR")
{
modalityForEdit = "MRI";
}
if (modality == "PT")
{
modalityForEdit = "PET";
}
if (modality == "PT、CT" || modality == "CT、PT")
{
modalityForEdit = "PET-CT";
}
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == g.Key, u => new SCPStudy() { Modalities = modality, ModalityForEdit = modalityForEdit });
}
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}维护数据结束");
}
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}接收中断,中断原因:{source.ToString() + reason.ToString()}");
/* nothing to do here */
}
public async void OnConnectionClosed(Exception exception)
{
/* nothing to do here */
//奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致
if (exception == null)
{
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
{
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
var ossService = _serviceProvider.GetService<IOSSService>();
var dicomArchiveService = _serviceProvider.GetService<IDicomArchiveService>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
long fileSize = 0;
try
{
using (MemoryStream ms = new MemoryStream())
{
await request.File.SaveAsync(ms);
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
}
catch (Exception ec)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
}
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(request.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
Console.WriteLine(seriesPath + " Id: " + seriesId);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e)
{
// let library handle logging and error response
return Task.CompletedTask;
}
public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request)
{
return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));
}
}
}
@@ -1,356 +0,0 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService : BaseService, IDicomArchiveService
{
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<SCPSeries> _seriesRepository;
private readonly IRepository<SCPInstance> _instanceRepository;
private readonly IRepository<Dictionary> _dictionaryRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
private List<Guid> _instanceIdList = new List<Guid>();
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
IRepository<SCPSeries> seriesRepository,
IRepository<SCPInstance> instanceRepository,
IRepository<Dictionary> dictionaryRepository,
IDistributedLockProvider distributedLockProvider)
{
_distributedLockProvider = distributedLockProvider;
_studyRepository = studyRepository;
_patientRepository = patientRepository;
_seriesRepository = seriesRepository;
_instanceRepository = instanceRepository;
_dictionaryRepository = dictionaryRepository;
}
/// <summary>
/// 单个文件接收 归档
/// </summary>
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
{
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
var isStudyNeedAdd = false;
var isSeriesNeedAdd = false;
var isInstanceNeedAdd = false;
var isPatientNeedAdd = false;
//var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
//using (@lock.Acquire())
{
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
if (findPatient == null && findStudy==null)
{
isPatientNeedAdd = true;
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId=trialId,
TrialSiteId=trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
EarliestStudyTime = studyTime,
LatestStudyTime = studyTime,
LatestPushTime = DateTime.Now,
};
if (findPatient.PatientBirthDate.Length == 8)
{
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
int year = 0;
var canParse = int.TryParse(yearStr, out year);
if (canParse && year > 1900)
{
findPatient.PatientBirthDate = birthDateStr;
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
if (studyTime.Value < birthDate.AddYears(patientAge))
{
patientAge--;
}
findPatient.PatientAge = patientAge.ToString();
}
}
else
{
findPatient.PatientBirthDate = string.Empty;
}
}
}
else
{
if (studyTime < findPatient.EarliestStudyTime)
{
findPatient.EarliestStudyTime = studyTime;
}
if (studyTime > findPatient.LatestStudyTime)
{
findPatient.LatestStudyTime = studyTime;
}
findPatient.LatestPushTime = DateTime.Now;
}
if (findStudy == null)
{
isStudyNeedAdd = true;
findStudy = new SCPStudy
{
CalledAE = calledAE,
CallingAE = callingAE,
PatientId = findPatient.Id,
Id = studyId,
TrialId = trialId,
TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime,
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
//ModalityForEdit = modalityForEdit,
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
InstitutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty),
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
//需要特殊处理
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
//IsDoubleReview = addtionalInfo.IsDoubleReview,
SeriesCount = 0,
InstanceCount = 0
};
if (findStudy.PatientBirthDate.Length == 8)
{
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
}
}
if (findSerice == null)
{
isSeriesNeedAdd = true;
findSerice = new SCPSeries
{
Id = seriesId,
StudyId = findStudy.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = seriesInstanceUid,
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
InstanceCount = 0
};
++findStudy.SeriesCount;
}
if (findInstance == null)
{
isInstanceNeedAdd = true;
findInstance = new SCPInstance
{
Id = instanceId,
StudyId = findStudy.Id,
SeriesId = findSerice.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = findSerice.SeriesInstanceUid,
SopInstanceUid = sopInstanceUid,
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
//InstanceTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.ContentDate) + dataset.GetSingleValue<string>(DicomTag.ContentTime), out DateTime dt) ? dt : null,
//InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate,(DateTime?)null)?.Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, TimeSpan.Zero)),
//dataset.GetSingleValueOrDefault(DicomTag.ContentDate,DateTime.Now);//, DicomTag.ContentTime)
CPIStatus = false,
ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
Path = fileRelativePath,
FileSize= fileSize,
};
++findStudy.InstanceCount;
++findSerice.InstanceCount;
}
if (isPatientNeedAdd)
{
var ss = await _patientRepository.AddAsync(findPatient);
}
if (isStudyNeedAdd)
{
var dd = await _studyRepository.AddAsync(findStudy);
}
else
{
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.Id, t => new SCPStudy() { IsUploadFinished = false });
}
if (isSeriesNeedAdd)
{
await _seriesRepository.AddAsync(findSerice);
}
if (isInstanceNeedAdd)
{
await _instanceRepository.AddAsync(findInstance);
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
}
await _studyRepository.SaveChangesAsync();
return findStudy.Id;
}
}
// 从DICOM文件中获取使用的字符集
private string GetEncodingVaulueFromDicomFile(DicomDataset dataset, DicomTag dicomTag)
{
// 获取DICOM文件的特定元素,通常用于指示使用的字符集
var charset = dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
var dicomEncoding = DicomEncoding.GetEncoding(charset);
var dicomStringElement = dataset.GetDicomItem<DicomStringElement>(dicomTag);
var bytes = dicomStringElement.Buffer.Data;
return dicomEncoding.GetString(bytes);
//// 从DICOM文件中获取使用的字符集
//string filePath = "C:\\Users\\hang\\Documents\\WeChat Files\\wxid_r2imdzb7j3q922\\FileStorage\\File\\2024-05\\1.2.840.113619.2.80.169103990.5390.1271401378.4.dcm";
//DicomFile dicomFile = DicomFile.Open(filePath);
//// 获取DICOM文件的特定元素,通常用于指示使用的字符集
//var charset = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
//var dicomEncoding = DicomEncoding.GetEncoding(charset);
//var value = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
//var dicomStringElement = dicomFile.Dataset.GetDicomItem<DicomStringElement>(DicomTag.PatientName);
//var bytes = dicomStringElement.Buffer.Data;
//var aa= dicomEncoding.GetString(bytes);
}
}
}
@@ -1,11 +0,0 @@
using FellowOakDicom;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace IRaCIS.Core.SCP.Service
{
public interface IDicomArchiveService
{
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
}
}
-770
View File
@@ -1,770 +0,0 @@
using AlibabaCloud.SDK.Sts20150401;
using Aliyun.OSS;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
using MassTransit;
using Microsoft.Extensions.Options;
using Minio;
using Minio.DataModel.Args;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
namespace IRaCIS.Core.SCP;
#region
[LowerCamelCaseJson]
public class MinIOOptions : AWSOptions
{
public int Port { get; set; }
}
public class AWSOptions
{
public string EndPoint { get; set; }
public bool UseSSL { get; set; }
public string AccessKeyId { get; set; }
public string RoleArn { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
public string Region { get; set; }
}
public class AliyunOSSOptions
{
public string RegionId { get; set; }
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string InternalEndpoint { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string RoleArn { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
}
public class ObjectStoreServiceOptions
{
public string ObjectStoreUse { get; set; }
public AliyunOSSOptions AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSOptions AWS { get; set; }
}
public class ObjectStoreDTO
{
public string ObjectStoreUse { get; set; }
public AliyunOSSTempToken AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSTempToken AWS { get; set; }
}
[LowerCamelCaseJson]
public class AliyunOSSTempToken
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public string SecurityToken { get; set; }
public DateTime Expiration { get; set; }
}
[LowerCamelCaseJson]
public class AWSTempToken
{
public string Region { get; set; }
public string SessionToken { get; set; }
public string EndPoint { get; set; }
public string AccessKeyId { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public DateTime? Expiration { get; set; }
}
public enum ObjectStoreUse
{
AliyunOSS = 0,
MinIO = 1,
AWS = 2,
}
#endregion
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
public interface IOSSService
{
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
public Task<string> GetSignedUrl(string ossRelativePath);
public Task DeleteFromPrefix(string prefix);
public ObjectStoreDTO GetObjectStoreTempToken();
}
public class OSSService : IOSSService
{
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
private AWSTempToken AWSTempToken { get; set; }
public OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options)
{
ObjectStoreServiceOptions = options.CurrentValue;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="fileStream"></param>
/// <param name="oosFolderPath"></param>
/// <param name="fileRealName"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
try
{
using (var memoryStream = new MemoryStream())
{
fileStream.Seek(0, SeekOrigin.Begin);
fileStream.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
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);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, memoryStream);
}
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();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithStreamData(memoryStream)
.WithObjectSize(memoryStream.Length);
await minioClient.PutObjectAsync(putObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
InputStream = memoryStream,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException($"上传发生异常:{ex.Message}");
}
return "/" + ossRelativePath;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="localFilePath"></param>
/// <param name="oosFolderPath"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var localFileName = Path.GetFileName(localFilePath);
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
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);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, localFilePath);
}
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();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFileName(localFilePath);
await minioClient.PutObjectAsync(putObjectArgs);
}
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.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
FilePath = localFilePath,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
return "/" + ossRelativePath;
}
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
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);
// 上传文件
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
// 将下载的文件流保存到本地文件
using (var fs = File.OpenWrite(localFilePath))
{
result.Content.CopyTo(fs);
fs.Close();
}
}
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();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFile(localFilePath);
await minioClient.GetObjectAsync(getObjectArgs);
}
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.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var getObjectArgs = new Amazon.S3.Model.GetObjectRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
};
await (await amazonS3Client.GetObjectAsync(getObjectArgs)).WriteResponseStreamToFileAsync(localFilePath, true, CancellationToken.None);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss下载失败!" + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
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);
// 生成签名URL。
var req = new GeneratePresignedUriRequest(aliConfig.BucketName, ossRelativePath, SignHttpMethod.Get)
{
// 设置签名URL过期时间,默认值为3600秒。
Expiration = DateTime.Now.AddHours(1),
};
var uri = _ossClient.GeneratePresignedUri(req);
return uri.PathAndQuery;
}
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();
var args = new PresignedGetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithExpiry(3600)
/*.WithHeaders(reqParams)*/;
var presignedUrl = await minioClient.PresignedGetObjectAsync(args);
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
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.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var presignedUrl = await amazonS3Client.GetPreSignedURLAsync(new GetPreSignedUrlRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
Expires = DateTime.UtcNow.AddMinutes(120)
});
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss授权url失败!" + ex.Message);
}
}
/// <summary>
/// 删除某个目录的文件
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
public async Task DeleteFromPrefix(string prefix)
{
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);
try
{
ObjectListing objectListing = null;
string nextMarker = null;
do
{
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName)
{
Prefix = prefix,
MaxKeys = 1000,
Marker = nextMarker
});
List<string> keys = objectListing.ObjectSummaries.Select(t => t.Key).ToList();
// 删除获取到的文件
if (keys.Count > 0)
{
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false));
}
// 设置 NextMarker 以获取下一页的数据
nextMarker = objectListing.NextMarker;
} while (objectListing.IsTruncated);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
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();
var listArgs = new ListObjectsArgs().WithBucket(minIOConfig.BucketName).WithPrefix(prefix).WithRecursive(true);
// 创建一个空列表用于存储对象键
var objects = new List<string>();
// 使用 await foreach 来异步迭代对象列表
await foreach (var item in minioClient.ListObjectsEnumAsync(listArgs))
{
objects.Add(item.Key);
}
if (objects.Count > 0)
{
var objArgs = new RemoveObjectsArgs()
.WithBucket(minIOConfig.BucketName)
.WithObjects(objects);
// 删除对象
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.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
// 列出指定前缀下的所有对象
var listObjectsRequest = new ListObjectsV2Request
{
BucketName = awsConfig.BucketName,
Prefix = prefix
};
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
if (listObjectsResponse.S3Objects.Count > 0)
{
// 准备删除请求
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
{
BucketName = awsConfig.BucketName,
Objects = new List<KeyVersion>()
};
foreach (var s3Object in listObjectsResponse.S3Objects)
{
deleteObjectsRequest.Objects.Add(new KeyVersion
{
Key = s3Object.Key
});
}
// 批量删除对象
var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);
}
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
public ObjectStoreDTO GetObjectStoreTempToken()
{
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
{
AccessKeyId = ossOptions.AccessKeyId,
AccessKeySecret = ossOptions.AccessKeySecret,
//AccessKeyId = "LTAI5tJV76pYX5yPg1N9QVE8",
//AccessKeySecret = "roRNLa9YG1of4pYruJGCNKBXEWTAWa",
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
});
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如oss-role-session。
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
var credentials = response.Body.Credentials;
var tempToken = new AliyunOSSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
AccessKeySecret = credentials.AccessKeySecret,
//转为服务器时区,最后统一转为客户端时区
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
SecurityToken = credentials.SecurityToken,
Region = ossOptions.Region,
BucketName = ossOptions.BucketName,
EndPoint = ossOptions.EndPoint,
ViewEndpoint = ossOptions.ViewEndpoint,
};
AliyunOSSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AliyunOSS = tempToken };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, MinIO = ObjectStoreServiceOptions.MinIO };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsOptions = ObjectStoreServiceOptions.AWS;
//aws 临时凭证
// 创建 STS 客户端
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
// 使用 AssumeRole 请求临时凭证
var assumeRoleRequest = new AssumeRoleRequest
{
RoleArn = awsOptions.RoleArn, // 角色 ARN
RoleSessionName = $"session-name-{NewId.NextGuid()}",
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
};
var assumeRoleResponse = stsClient.AssumeRoleAsync(assumeRoleRequest).Result;
var credentials = assumeRoleResponse.Credentials;
var tempToken = new AWSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
SecretAccessKey = credentials.SecretAccessKey,
SessionToken = credentials.SessionToken,
Expiration = credentials.Expiration,
Region = awsOptions.Region,
BucketName = awsOptions.BucketName,
EndPoint = awsOptions.EndPoint,
ViewEndpoint = awsOptions.ViewEndpoint,
};
AWSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AWS = tempToken };
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -1,175 +0,0 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.SCP.Service;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace IRaCIS.Core.SCP;
public class SyncFileRecoveryService(IServiceScopeFactory _scopeFactory, FileSyncQueue _fileSyncQueue) : BackgroundService
{
private readonly int _pageSize = 500;
/// <summary>
/// 多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//在本地调试的时候,不干涉部署同步任务
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
await Task.CompletedTask;
//using var scope = _scopeFactory.CreateScope();
//var fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
//// 延迟启动,保证主机快速启动
//await Task.Delay(5000, stoppingToken);
//int page = 0;
//while (!stoppingToken.IsCancellationRequested)
//{
// // 分页获取未入队任务
// var pending = await fileUploadRecordRepository
// .Where(x => x.IsNeedSync == true && (x.IsSync == false || x.IsSync == null))
// .OrderByDescending(x => x.Priority)
// .Select(t => new { t.Id, t.Priority })
// .Skip(page * _pageSize)
// .Take(_pageSize)
// .ToListAsync(stoppingToken);
// if (!pending.Any())
// break; // 扫描完毕,退出循环
// foreach (var file in pending)
// {
// //file.IsQueued = true; // 避免重复入队
// _fileSyncQueue.Enqueue(file.Id, file.Priority ?? 0); // 放入队列
// }
// page++; // 下一页
// await Task.Delay(200, stoppingToken); // 缓解数据库压力
//}
}
}
public class FileSyncWorker(IServiceScopeFactory _scopeFactory, ILogger<FileSyncWorker> _logger, FileSyncQueue _fileSyncQueue) : BackgroundService
{
// ⭐ 自动根据服务器CPU
private readonly int _workerCount = Math.Max(1, Environment.ProcessorCount - 1);
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
for (int i = 0; i < _workerCount; i++)
Task.Run(() => WorkerLoop(stoppingToken));
return Task.CompletedTask;
}
private async Task WorkerLoop(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var id = await _fileSyncQueue.DequeueAsync(stoppingToken);
try
{
using var scope = _scopeFactory.CreateScope();
var _fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
var _uploadFileSyncRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UploadFileSyncRecord>>();
var syncConfig = (scope.ServiceProvider.GetRequiredService<IOptionsMonitor<ObjectStoreServiceOptions>>()).CurrentValue;
var oss = scope.ServiceProvider.GetRequiredService<IOSSService>();
var file = await _fileUploadRecordRepository.FirstOrDefaultAsync(t => t.Id == id);
// ✅ 不要 return
if (file == null || file.IsNeedSync != true || syncConfig.IsOpenStoreSync == false)
{
continue;
}
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
continue;
}
//如果发现系统配置某一边同步进行了关闭,那么就直接返回,不执行任务
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
return;
}
var log = new UploadFileSyncRecord
{
FileUploadRecordId = id,
StartTime = DateTime.Now,
JobState = jobState.RUNNING
};
await _uploadFileSyncRecordRepository.AddAsync(log);
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
try
{
await oss.SyncFileAsync(file.Path.TrimStart('/'), file.UploadRegion == "CN" ? ObjectStoreUse.AliyunOSS : ObjectStoreUse.AWS, file.UploadRegion == "CN" ? ObjectStoreUse.AWS : ObjectStoreUse.AliyunOSS);
file.IsSync = true;
file.SyncFinishedTime = DateTime.Now;
log.JobState = jobState.SUCCESS;
}
catch (Exception ex)
{
log.JobState = jobState.FAILED;
log.Msg = ex.Message.Length > 300 ? ex.Message.Substring(0, 300) : ex.Message;
}
log.EndTime = DateTime.Now;
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync failed {Id}", id);
}
finally
{
// ⭐⭐⭐ 永远执行
_fileSyncQueue.Complete(id);
}
}
}
}
+16 -16
View File
@@ -7,28 +7,28 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DistributedLock.Core" Version="1.0.9" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.7" />
<PackageReference Include="fo-dicom" Version="5.2.6" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.7" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.6" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="Minio" Version="7.0.0" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.9.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
<ItemGroup>
-8
View File
@@ -63,10 +63,6 @@ builder.Host
#region
var _configuration = builder.Configuration;
builder.Services.AddSingleton<FileSyncQueue>();
builder.Services.AddHostedService<SyncFileRecoveryService>();
builder.Services.AddHostedService<FileSyncWorker>();
//健康检查
builder.Services.AddHealthChecks();
@@ -117,9 +113,6 @@ builder.Services.AddAutoMapper(automapper =>
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
// FusionCache
builder.Services.AddFusionCache();
builder.Services.AddMediator(cfg =>
{
@@ -198,7 +191,6 @@ app.MapControllers();
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
+107 -487
View File
@@ -1,30 +1,26 @@
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.IO.Buffer;
using FellowOakDicom.Network;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using Medallion.Threading;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using FellowOakDicom.Network;
using FellowOakDicom;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using IRaCIS.Core.Domain.Share;
using Serilog;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Options;
using System.Data;
using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Core.SCP.Service
{
@@ -43,9 +39,7 @@ namespace IRaCIS.Core.SCP.Service
{
private IServiceProvider _serviceProvider { get; set; }
private List<Guid> _SCPStudyIdList => _ImageUploadList.Where(t => t.SCPStudyId != Guid.Empty).Select(t => t.SCPStudyId).ToList();
private List<ImageUploadInfo> _ImageUploadList { get; set; } = new List<ImageUploadInfo>();
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
private SCPImageUpload _upload { get; set; }
@@ -53,8 +47,6 @@ namespace IRaCIS.Core.SCP.Service
private Guid _trialSiteId { get; set; }
private bool _releasedNormally = false;
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
@@ -96,7 +88,7 @@ namespace IRaCIS.Core.SCP.Service
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
{
_upload = new SCPImageUpload() { Id = NewId.NextSequentialGuid(), StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
@@ -122,7 +114,7 @@ namespace IRaCIS.Core.SCP.Service
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId == _trialId).FirstOrDefault();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
@@ -131,7 +123,7 @@ namespace IRaCIS.Core.SCP.Service
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
@@ -170,61 +162,27 @@ namespace IRaCIS.Core.SCP.Service
public async Task OnReceiveAssociationReleaseRequestAsync()
{
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
await DataMaintenanceAsaync();
var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}");
using (await @lock.AcquireAsync())
{
await DataMaintenanceAsaync();
//记录监控
await AddUploadLogAsync();
_releasedNormally = true;
Log.Logger.Information($"进入释放连接请求 {_releasedNormally}");
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
await SendAssociationReleaseResponseAsync();
}
private async Task AddUploadLogAsync()
{
//记录监控
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
//有时候没有建立连接会走关闭通过日志发现
if (_upload != null)
{
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _ImageUploadList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
//可能是测试echo 导致记录了
if (_upload.FileCount > 0)
{
//可能是测试echo 导致记录了
await _SCPImageUploadRepository.AddAsync(_upload, true);
}
}
await _SCPImageUploadRepository.AddAsync(_upload, true);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
@@ -279,25 +237,17 @@ namespace IRaCIS.Core.SCP.Service
public async void OnConnectionClosed(Exception exception)
{
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
/* nothing to do here */
if (exception != null || _releasedNormally == false)
//奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致
if (exception == null)
{
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true });
//记录日志
await AddUploadLogAsync();
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
else
{
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false });
}
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
@@ -308,23 +258,9 @@ namespace IRaCIS.Core.SCP.Service
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
{
string studyInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
string seriesInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
string sopInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
string patientIdStr = request.Dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
if (studyInstanceUid.IsNullOrEmpty() || seriesInstanceUid.IsNullOrEmpty() || sopInstanceUid.IsNullOrEmpty())
{
Log.Logger.Error($"接收数据读取StudyInstanceUID{studyInstanceUid}、SeriesInstanceUID{seriesInstanceUid}、SOPInstanceUID{sopInstanceUid}有空 ");
return new DicomCStoreResponse(request, DicomStatus.Success);
}
//确保来了影像集合存在
if (!_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
_ImageUploadList.Add(new ImageUploadInfo() { StudyInstanceUid = studyInstanceUid });
}
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
@@ -336,11 +272,6 @@ namespace IRaCIS.Core.SCP.Service
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var _fusionCache = _serviceProvider.GetService<IFusionCache>();
var _trialSiteRepository = _serviceProvider.GetService<IRepository<TrialSite>>();
var _systemAnonymizationRepository = _serviceProvider.GetService<IRepository<SystemAnonymization>>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
@@ -349,383 +280,15 @@ namespace IRaCIS.Core.SCP.Service
long fileSize = 0;
try
{
// 直接拿 Dataset(已经完整)
var dataset = request.Dataset;
#region
var anonymizeList = await _fusionCache.GetOrSetAsync(CacheKeys.SystemAnonymization, _ => CacheHelper.GetSystemAnonymizationListAsync(_systemAnonymizationRepository), TimeSpan.FromDays(7));
var trialSiteInfo = await _fusionCache.GetOrSetAsync(CacheKeys.TrialSiteInfo(_trialSiteId), _ => CacheHelper.GetTrialSiteInfo(_trialSiteId, _trialSiteRepository), TimeSpan.FromMinutes(2));
var fixedFiledList = anonymizeList.Where(t => t.IsFixed).ToList();
var ircFiledList = anonymizeList.Where(t => t.IsFixed == false).ToList();
foreach (var item in fixedFiledList)
using (MemoryStream ms = new MemoryStream())
{
await request.File.SaveAsync(ms);
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
dataset.AddOrUpdate(dicomTag, item.ReplaceValue);
}
foreach (var item in ircFiledList)
{
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
if (dicomTag == DicomTag.ClinicalTrialProtocolID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialProtocolID, trialSiteInfo.TrialCode);
}
if (dicomTag == DicomTag.ClinicalTrialSiteID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSiteID, trialSiteInfo.TrialSiteCode);
}
if (dicomTag == DicomTag.ClinicalTrialSubjectID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSubjectID, "");
}
if (dicomTag == DicomTag.ClinicalTrialTimePointID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialTimePointID, "");
}
if (dicomTag == DicomTag.PatientID)
{
var pid = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
dataset.AddOrUpdate(DicomTag.PatientID, trialSiteInfo.TrialCode + "-" + pid);
}
}
#endregion
// 构造 DicomFile(不用 Open
var dicomFile = new DicomFile(dataset);
#region 1便
var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
//多帧处理逻辑
if (numberOfFrames > 1)
{
//一定要有像素数据才处理
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
if (pixelData != null)
{
try
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 开始处理多帧instanceId:{instanceId}");
var syntax = pixelData.Syntax;
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
int fragmentSize = 20 * 1024;
var frag = dicomFile.Dataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData);
int fragmentCount = frag?.Fragments?.Count() ?? 0;
var originOffsetTable = frag?.OffsetTable; //有可能没有表,需要自己重建
var bot = new List<uint>();
uint botOffset = 0;
//需要拆成固定片段的
if (syntax.IsEncapsulated && fragmentCount == pixelData.NumberOfFrames && numberOfFrames > 1)
{
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
#region test
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
// var data = frame.Data;
// int offset = 0;
// while (offset < data.Length)
// {
// int size = Math.Min(fragmentSize, data.Length - offset);
// var buffer = new byte[size];
// Buffer.BlockCopy(data, offset, buffer, 0, size);
// newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
// offset += size;
// }
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//newFragments.OffsetTable.AddRange(newOffsetTable.ToArray());
#endregion
#region test fo-dicom auto bot
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//Console.WriteLine(newOffsetTable.ToJsonStr());
#endregion
#region 使
for (int n = 0; n < pixelData.NumberOfFrames; n++)
{
var frameData = pixelData.GetFrame(n); // 获取完整一帧
var data = frameData.Data;
int offset = 0;
bot.Add(botOffset);
botOffset += (uint)data.Length;
while (offset < data.Length)
{
botOffset += 8;
int size = Math.Min(fragmentSize, data.Length - offset);
var buffer = new byte[size];
Buffer.BlockCopy(data, offset, buffer, 0, size);
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
offset += size;
}
}
//保留原始偏移表
//if (originOffsetTable.Count == pixelData.NumberOfFrames)
//{
// newFragments.OffsetTable.AddRange(originOffsetTable.ToArray());
//}
//else
{
newFragments.OffsetTable.AddRange(bot.ToArray());
//Console.WriteLine(bot.ToJsonStr());
}
#endregion
dicomFile.Dataset.AddOrUpdate(newFragments);
}
//传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败
else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0)
{
uint offset = 0;
bot.Add(offset);
var fistSize = frag.Fragments.FirstOrDefault()?.Size ?? 0;
//和上一个大小不一样
var isDiffrentBefore = false;
uint count = 0;
// 假设你知道每帧对应的 fragment 数量
foreach (var frameFragments in frag.Fragments)
{
count++;
if (frameFragments.Size == fistSize)
{
isDiffrentBefore = false;
// 累加这一帧所有 fragment 的大小
offset += (uint)frameFragments.Size;
continue;
}
else
{
offset += (uint)frameFragments.Size;
isDiffrentBefore = true;
}
if (isDiffrentBefore)
{
//每个Fragment 也占用字节
offset += 8 * count;
bot.Add(offset);
count = 0;
}
}
bot.RemoveAt(bot.Count - 1);
// 设置到新的 PixelData
frag.OffsetTable.AddRange(bot.ToArray());
}
}
catch (Exception mutiEx)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 处理多帧失败,上传原始文件:{mutiEx.ToString()}");
}
}
}
#endregion
#region
//// --- 保存到本地文件测试 ---
//var localPath = @"D:\TestDicom.dcm";
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
//{
// ms.CopyTo(fs);
//}
//return new DicomCStoreResponse(request, DicomStatus.Success);
#endregion
// 直接写入内存
await using var ms = new MemoryStream();
await dicomFile.SaveAsync(ms);
ms.Position = 0;
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = _trialId, BatchDataType = BatchDataType.PACSReceive, UploadBatchId = _upload.Id.ToString() });
fileSize = ms.Length;
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(dicomFile, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE, fileSize);
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(dicomFile.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, $"{seriesId.ToString()}_{instanceId.ToString()}.preview.jpg", false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = _trialId, BatchDataType = BatchDataType.PACSReceive });
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.SuccessImageCount++;
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
{
find.PatientNameList.Add(patientIdStr);
}
//首次 (默认是Guid 空,数据库归档出了Id)
if (find.SCPStudyId != scpStudyId)
{
find.SCPStudyId = scpStudyId;
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.FailedImageCount++;
}
}
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
@@ -736,6 +299,63 @@ namespace IRaCIS.Core.SCP.Service
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
}
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(request.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
Console.WriteLine(seriesPath + " Id: " + seriesId);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
-103
View File
@@ -1,103 +0,0 @@
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.EntityFrameworkCore;
namespace IRaCIS.Core.SCP.Service;
public static class CacheKeys
{
//项目缓存
public static string Trial(string trialIdStr) => $"TrialId:{trialIdStr}";
//检查编号递增锁
public static string TrialStudyMaxCode(Guid trialId) => $"TrialStudyMaxCode:{trialId}";
public static string TrialStudyUidUploading(Guid trialId, string studyUid) => $"TrialStudyUid:{trialId}_{studyUid}";
//CRC上传影像提交锁key
public static string TrialStudyUidDBLock(Guid trialId, string studyUid) => $"TrialStudyUidDBLock:{trialId}_{studyUid}";
public static string TrialTaskStudyUidUploading(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialStudyUid:{trialId}_{visiTaskId}_{studyUid}";
//影像后处理上传提交锁key
public static string TrialTaskStudyUidDBLock(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialTaskStudyUidDBLock:{trialId}_{visiTaskId}_{studyUid}";
//系统匿名化
public static string SystemAnonymization => $"SystemAnonymization";
//前端国际化
public static string FrontInternational => $"FrontInternationalList";
//登录挤账号
public static string UserToken(Guid userId) => $"UserToken:{userId}";
//超时没请求接口自动退出
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
public static string UserDisable(Guid userId) => $"UserDisable:{userId}";
public static string UserRoleDisable(Guid userRoleId) => $"UserRoleDisable:{userRoleId}";
/// <summary>
/// 用户登录错误 限制登录
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static string UserLoginError(string userName) => $"login-failures:{userName}";
/// <summary>
/// 跳过阅片
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string SkipReadingCacheKey(Guid userId) => $"{userId}SkipReadingCache";
/// <summary>
/// 开始阅片时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartReadingTimeKey(Guid userId) => $"{userId}StartReadingTime";
/// <summary>
/// 开始休息时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId, string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string TrialSiteInfo(Guid trialSiteId) => $"{trialSiteId}TrialSiteInfo";
public static string TrialDataStoreType(Guid trialId) => $"TrialDataStoreType:{trialId}";
}
public static class CacheHelper
{
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
{
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync();
return statusStr;
}
public class TrialSiteInfoDTO
{
public string TrialSiteCode { get; set; }
public string TrialCode { get; set; }
}
public static async Task<TrialSiteInfoDTO> GetTrialSiteInfo(Guid trialSiteId, IRepository<TrialSite> _trialSiteRepository)
{
var obj = await _trialSiteRepository.Where(t => t.Id == trialSiteId, ignoreQueryFilters: true).Select(t => new TrialSiteInfoDTO { TrialCode= t.Trial.TrialCode, TrialSiteCode= t.TrialSiteCode }).FirstOrDefaultAsync();
return obj??new TrialSiteInfoDTO();
}
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
{
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
return list;
}
}
+51 -136
View File
@@ -1,32 +1,48 @@
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using FellowOakDicom.Network;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using Medallion.Threading;
using Microsoft.AspNetCore.Hosting;
using Serilog.Sinks.File;
using System.Data;
using System.Runtime.Intrinsics.X86;
using IRaCIS.Core.Domain.Share;
using System.Text;
using ZiggyCreatures.Caching.Fusion;
using static IRaCIS.Core.SCP.Service.CacheHelper;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService(IRepository<SCPPatient> _patientRepository,
IRepository<SCPStudy> _studyRepository,
IRepository<SCPSeries> _seriesRepository,
IRepository<SCPInstance> _instanceRepository
) : BaseService, IDicomArchiveService
public class DicomArchiveService : BaseService, IDicomArchiveService
{
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<SCPSeries> _seriesRepository;
private readonly IRepository<SCPInstance> _instanceRepository;
private readonly IRepository<Dictionary> _dictionaryRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
private List<Guid> _instanceIdList = new List<Guid>();
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
IRepository<SCPSeries> seriesRepository,
IRepository<SCPInstance> instanceRepository,
IRepository<Dictionary> dictionaryRepository,
IDistributedLockProvider distributedLockProvider)
{
_distributedLockProvider = distributedLockProvider;
_studyRepository = studyRepository;
_patientRepository = patientRepository;
_seriesRepository = seriesRepository;
_instanceRepository = instanceRepository;
_dictionaryRepository = dictionaryRepository;
}
@@ -36,19 +52,16 @@ namespace IRaCIS.Core.SCP.Service
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE, long fileSize)
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
{
var dataset = dicomFile.Dataset;
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
@@ -61,15 +74,15 @@ namespace IRaCIS.Core.SCP.Service
//using (@lock.Acquire())
{
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId == trialSiteId);
var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
if (findPatient == null /*&& findStudy==null*/)
if (findPatient == null && findStudy==null)
{
isPatientNeedAdd = true;
@@ -77,8 +90,8 @@ namespace IRaCIS.Core.SCP.Service
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId = trialId,
TrialSiteId = trialSiteId,
TrialId=trialId,
TrialSiteId=trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
@@ -106,7 +119,7 @@ namespace IRaCIS.Core.SCP.Service
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate, out birthDate))
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
@@ -138,32 +151,6 @@ namespace IRaCIS.Core.SCP.Service
}
findPatient.LatestPushTime = DateTime.Now;
findPatient.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
findPatient.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
findPatient.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
findPatient.PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty);
if (findPatient.PatientBirthDate.Length == 8)
{
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
int year = 0;
var canParse = int.TryParse(yearStr, out year);
if (canParse && year > 1900)
{
findPatient.PatientBirthDate = birthDateStr;
}
else
{
findPatient.PatientBirthDate = string.Empty;
}
}
}
if (findStudy == null)
@@ -180,9 +167,6 @@ namespace IRaCIS.Core.SCP.Service
TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime,
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
//ModalityForEdit = modalityForEdit,
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
@@ -204,18 +188,6 @@ namespace IRaCIS.Core.SCP.Service
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
RadionuclideTotalDose= dataset.GetSingleValueOrDefault(DicomTag.RadionuclideTotalDose, string.Empty),
RadionuclideHalfLife= dataset.GetSingleValueOrDefault(DicomTag.RadionuclideHalfLife, string.Empty),
RadiopharmaceuticalStartTime = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalStartTime, string.Empty),
Manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty),
ManufacturerModelName = dataset.GetSingleValueOrDefault(DicomTag.ManufacturerModelName, string.Empty),
DeviceSerialNumber = dataset.GetSingleValueOrDefault(DicomTag.DeviceSerialNumber, string.Empty),
DeviceUID = dataset.GetSingleValueOrDefault(DicomTag.DeviceUID, string.Empty),
SoftwareVersions = dataset.GetSingleValueOrDefault(DicomTag.SoftwareVersions, string.Empty),
PatientWeight = dataset.GetSingleValueOrDefault(DicomTag.PatientWeight, string.Empty),
//IsDoubleReview = addtionalInfo.IsDoubleReview,
@@ -229,20 +201,6 @@ namespace IRaCIS.Core.SCP.Service
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
}
}
else
{
findStudy.DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty);
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
findStudy.CalledAE = calledAE;
findStudy.CallingAE = callingAE;
findStudy.PatientIdStr = patientIdStr;
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
findStudy.UpdateTime = DateTime.Now;
await _patientRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.PatientId, u => new SCPPatient() { LatestPushTime = DateTime.Now });
}
if (findSerice == null)
@@ -260,9 +218,6 @@ namespace IRaCIS.Core.SCP.Service
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
@@ -278,29 +233,13 @@ namespace IRaCIS.Core.SCP.Service
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
RadiopharmaceuticalInformationSequence = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalInformationSequence, string.Empty),
AcquisitionDate = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionDate, string.Empty),
InstanceCount = 0
};
++findStudy.SeriesCount;
}
else
{
findSerice.DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty);
findSerice.DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty);
findSerice.UpdateTime = DateTime.Now;
}
var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
var isEncapsulated = false;
if (transferSyntaxUID.IsNotNullOrEmpty())
{
isEncapsulated = DicomTransferSyntax.Lookup(DicomUID.Parse(transferSyntaxUID)).IsEncapsulated;
}
if (findInstance == null)
{
@@ -325,46 +264,22 @@ namespace IRaCIS.Core.SCP.Service
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
PhotometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty),
BitsAllocated = dataset.GetSingleValueOrDefault(DicomTag.BitsAllocated, 0),
PixelRepresentation = dataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, string.Empty),
RescaleIntercept = dataset.GetSingleValueOrDefault(DicomTag.RescaleIntercept, string.Empty),
RescaleSlope = dataset.GetSingleValueOrDefault(DicomTag.RescaleSlope, string.Empty),
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
SequenceOfUltrasoundRegions = dataset.GetSingleValueOrDefault(DicomTag.SequenceOfUltrasoundRegions, string.Empty),
FrameTime = dataset.GetSingleValueOrDefault(DicomTag.FrameTime, string.Empty),
CorrectedImage = dataset.GetSingleValueOrDefault(DicomTag.CorrectedImage, string.Empty),
Units = dataset.GetSingleValueOrDefault(DicomTag.Units, string.Empty),
DecayCorrection = dataset.GetSingleValueOrDefault(DicomTag.DecayCorrection, string.Empty),
EncapsulatedDocument = dataset.GetSingleValueOrDefault(DicomTag.EncapsulatedDocument, string.Empty),
Path = fileRelativePath,
FileSize = fileSize,
FileSize= fileSize,
};
++findStudy.InstanceCount;
++findSerice.InstanceCount;
}
else
{
findInstance.SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
findInstance.MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
findInstance.TransferSytaxUID = transferSyntaxUID;
findInstance.MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
findInstance.IsEncapsulated = isEncapsulated;
findInstance.UpdateTime = DateTime.Now;
}
if (isPatientNeedAdd)
{
@@ -389,7 +304,7 @@ namespace IRaCIS.Core.SCP.Service
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath, FileSize = fileSize });
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
}
await _studyRepository.SaveChangesAsync();
@@ -1,334 +0,0 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2026-03-10 06:15:17Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using AutoMapper;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infra.EFCore.Common;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP;
using IRaCIS.Core.SCP.Service;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System.Drawing;
using System.Threading.Channels;
using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
namespace IRaCIS.Core.SCP.Service;
public class FileUploadRecordAddOrEdit
{
public Guid? Id { get; set; }
public string FileName { get; set; }
public long FileSize { get; set; }
public string FileType { get; set; }
public string Path { get; set; }
public string UploadBatchId { get; set; }
public BatchDataType BatchDataType { get; set; }
public string StudyCode { get; set; }
public Guid? TrialId { get; set; }
public Guid? SubjectId { get; set; }
public Guid? SubjectVisitId { get; set; }
public Guid? DicomStudyId { get; set; }
public Guid? NoneDicomStudyId { get; set; }
public string FileMarkId { get; set; }
public int? Priority { get; set; }
public string IP { get; set; }
public bool? IsNeedSync { get; set; }
public string UploadRegion { get; set; }
public string TargetRegion { get; set; }
public bool? IsSync { get; set; }
}
public interface IFileUploadRecordService
{
Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord);
}
[ApiExplorerSettings(GroupName = "Common")]
public class FileUploadRecordService(IRepository<FileUploadRecord> _fileUploadRecordRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<ObjectStoreServiceOptions> options,
IFusionCache _fusionCache, IRepository<Trial> _trialRepository, FileSyncQueue _fileSyncQueue) : BaseService, IFileUploadRecordService
{
ObjectStoreServiceOptions ObjectStoreServiceConfig = options.CurrentValue;
public async Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord)
{
addOrEditFileUploadRecord.IP = _userInfo.IP;
if (ObjectStoreServiceConfig.IsOpenStoreSync && _userInfo.Domain.IsNotNullOrEmpty())
{
var find = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.Domain == _userInfo.Domain);
if (find != null)
{
addOrEditFileUploadRecord.UploadRegion = find.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = find.TargetRegion;
}
else
{
//前后端调试的时候,上传的时候域名不对应,自动按照后端配置设置上传区域和同步区域
var apiDefalut = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.UploadRegion == ObjectStoreServiceConfig.ApiDeployRegion);
if (apiDefalut != null)
{
addOrEditFileUploadRecord.UploadRegion = apiDefalut.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = apiDefalut.TargetRegion;
}
}
}
//SCP推送过来IP为空,后端归档的,设置区域
if (_userInfo.IP.IsNullOrEmpty() && _userInfo.Domain.IsNullOrEmpty())
{
var apiDefalut = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.UploadRegion == ObjectStoreServiceConfig.ApiDeployRegion);
if (apiDefalut != null)
{
addOrEditFileUploadRecord.UploadRegion = apiDefalut.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = apiDefalut.TargetRegion;
}
}
if (addOrEditFileUploadRecord.TrialId != null)
{
var trialDataStore = await _fusionCache.GetOrSetAsync(CacheKeys.TrialDataStoreType(addOrEditFileUploadRecord.TrialId.Value), async _ =>
{
return await _trialRepository.Where(t => t.Id == addOrEditFileUploadRecord.TrialId).Select(t => t.TrialDataStoreType)
.FirstOrDefaultAsync();
},
TimeSpan.FromDays(7)
);
//项目配置了,那么就设置需要同步
if (trialDataStore == TrialDataStore.MUtiCenter)
{
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.Priority = addOrEditFileUploadRecord.Priority ?? 0;
addOrEditFileUploadRecord.IsSync = false;
}
else
{
addOrEditFileUploadRecord.IsNeedSync = false;
//addOrEditFileUploadRecord.TargetRegion = "";
}
}
else
{
//系统文件,默认同步
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.IsSync = false;
addOrEditFileUploadRecord.Priority = addOrEditFileUploadRecord.Priority ?? 0;
}
var entity = await _fileUploadRecordRepository.InsertOrUpdateAsync(addOrEditFileUploadRecord, true);
if (addOrEditFileUploadRecord.IsNeedSync == true)
{
_fileSyncQueue.Enqueue(entity.Id, addOrEditFileUploadRecord.Priority ?? 0);
}
return ResponseOutput.Ok(entity.Id.ToString());
}
}
public sealed class FileSyncQueue
{
/// <summary>
/// 优先级队列(仅负责排序)
/// </summary>
private readonly PriorityQueue<Guid, int> _queue = new();
/// <summary>
/// 当前等待中的任务(唯一真实数据)
/// key = Guid
/// value = 最新 priority
/// </summary>
private readonly Dictionary<Guid, int> _waiting = new();
/// <summary>
/// 正在执行的任务(防止重复执行)
/// </summary>
private readonly HashSet<Guid> _running = new();
/// <summary>
/// worker 等待信号
/// </summary>
private readonly SemaphoreSlim _signal = new(0);
private readonly object _lock = new();
// ============================================================
// Enqueue
// ============================================================
/// <summary>
/// 入队(同 Guid 会覆盖优先级)
/// </summary>
public void Enqueue(Guid id, int priority)
{
bool needSignal = false;
lock (_lock)
{
// 如果正在执行,忽略(防止重复)
if (_running.Contains(id))
return;
// 是否新任务(用于减少 signal 风暴)
if (!_waiting.ContainsKey(id))
needSignal = true;
// 更新为最新优先级(最后一次为准)
_waiting[id] = priority; //等价于添加或者更新
// PriorityQueue 无法更新节点
// 允许旧节点存在,Dequeue 时过滤
_queue.Enqueue(id, -priority);
}
// 只有新增任务才唤醒 worker
if (needSignal)
_signal.Release();
}
// ============================================================
// Dequeue
// ============================================================
/// <summary>
/// 获取一个待执行任务(无任务时自动等待)
/// </summary>
public async Task<Guid> DequeueAsync(CancellationToken ct)
{
while (true)
{
await _signal.WaitAsync(ct);
lock (_lock)
{
while (_queue.Count > 0)
{
var id = _queue.Dequeue();
// 已被覆盖或取消
if (!_waiting.TryGetValue(id, out _))
continue;
// 标记为运行中
_waiting.Remove(id);
_running.Add(id);
return id;
}
}
}
}
// ============================================================
// Complete
// ============================================================
/// <summary>
/// 任务执行完成(必须调用)
/// </summary>
public void Complete(Guid id)
{
lock (_lock)
{
_running.Remove(id);
}
}
// ============================================================
// Snapshot
// ============================================================
/// <summary>
/// 当前等待中的任务快照
/// </summary>
public Guid[] Snapshot()
{
lock (_lock)
{
return _waiting.Keys.ToArray();
}
}
// ============================================================
// 状态信息(调试用)
// ============================================================
public int WaitingCount
{
get
{
lock (_lock)
return _waiting.Count;
}
}
public int RunningCount
{
get
{
lock (_lock)
return _running.Count;
}
}
}
@@ -5,7 +5,7 @@ namespace IRaCIS.Core.SCP.Service
{
public interface IDicomArchiveService
{
Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
}
}
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
using AutoMapper;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.SCP.Service;
namespace IRaCIS.Core.Application.Service
{
public class CommonConfig : Profile
{
public CommonConfig()
{
CreateMap<FileUploadRecordAddOrEdit, FileUploadRecord>().ReverseMap();
}
}
}
@@ -1,37 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"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",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
}
},
"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"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP"
],
"ServerPort": 11112
}
}
@@ -8,26 +8,6 @@
},
"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",
@@ -39,18 +19,6 @@
"ViewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
"UseSSL": true,
"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
}
},
@@ -7,28 +7,7 @@
}
},
"ObjectStoreService": {
// 使
"ObjectStoreUse": "AliyunOSS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "CN",
"SyncConfigList": [
{
"Domain": "irc.test.extimaging.com",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.test.extimaging.com",
"Primary": "AWS",
"Target": "AliyunOSS",
"UploadRegion": "US",
"TargetRegion": "CN",
"IsOpenSync": true
}
],
"AliyunOSS": {
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
@@ -50,27 +29,6 @@
"secretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
"bucketName": "hir-test",
"viewEndpoint": "http://106.14.89.110:9001/hir-test/"
},
// AWS S3
"AWS": {
// AWS S3 Region
"Region": "us-east-1",
// AWS S3 访
"EndPoint": "s3.us-east-1.amazonaws.com",
// 使 SSL
"UseSSL": true,
// AWS S3 ARN
"RoleArn": "arn:aws:iam::471112624751:role/uat_s3_access",
// AWS S3 访 ID
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
// AWS S3 访 Secret
"SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
// AWS S3 Bucket
"BucketName": "ei-med-s3-lili-uat-store",
// AWS S3 访
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com",
// AWS S3
"DurationSeconds": 7200
}
},
-32
View File
@@ -8,38 +8,6 @@
},
"ObjectStoreService": {
"ObjectStoreUse": "AWS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "US",
"SyncConfigList": [
{
"Domain": "eilili.elevateimaging.ai",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.elevateimaging.ai",
"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": "LTAI5tNRTsqL6aWmHkDmTwoH",
"AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH",
"RoleArn": "acs:ram::1899121822495495:role/irc-oss-access",
"BucketName": "zy-lili-store",
"ViewEndpoint": "https://zy-lili-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
+5 -46
View File
@@ -8,58 +8,17 @@
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "CN",
"SyncConfigList": [
{
"Domain": "irc.uat.extimaging.com",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.uat.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": "LTAI5tRRZehUp2V9pyTPtAJm",
"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",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
"BucketName": "tl-med-irc-uat-store",
"ViewEndpoint": "https://tl-med-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
// AWS S3
"AWS": {
// AWS S3 Region
"Region": "us-east-1",
// AWS S3 访
"EndPoint": "s3.us-east-1.amazonaws.com",
// 使 SSL
"UseSSL": true,
// AWS S3 ARN
"RoleArn": "arn:aws:iam::471112624751:role/uat_s3_access",
// AWS S3 访 ID
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
// AWS S3 访 Secret
"SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
// AWS S3 Bucket
"BucketName": "ei-med-s3-lili-uat-store",
// AWS S3 访
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com",
// AWS S3
"DurationSeconds": 7200
}
},
"ConnectionStrings": {
-6
View File
@@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRC.Core.Dicom", "IRC.Core.Dicom\IRC.Core.Dicom.csproj", "{0545F0A5-D97B-4A47-92A6-A8A02A181322}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -59,10 +57,6 @@ Global
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.Build.0 = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+4 -10
View File
@@ -25,7 +25,6 @@ using Microsoft.Extensions.Options;
using Org.BouncyCastle.Tls;
using RestSharp;
using RestSharp.Authenticators;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -127,7 +126,7 @@ namespace IRaCIS.Api.Controllers
var token = _tokenService.GetToken(new UserTokenInfo()
{
IdentityUserId = Guid.NewGuid(),
UserName = "ImageShare",
UserName = "Share001",
UserTypeEnum = UserTypeEnum.ShareImage,
});
@@ -138,14 +137,9 @@ namespace IRaCIS.Api.Controllers
public async Task<IResponseOutput> GetObjectStoreTokenAsync([FromServices] IOptionsMonitor<ObjectStoreServiceOptions> options, [FromServices] IOSSService _oSSService)
{
var domain = HttpContext.Request.Host.Host;
var result = _oSSService.GetObjectStoreTempToken();
var result = _oSSService.GetObjectStoreTempToken(domain);
Log.Logger.Information($"使用域名:{domain}请求token.返回{result.ToJsonStr()}");
//result.AWS = await GetAWSTemToken(options.CurrentValue);
return ResponseOutput.Ok(result);
@@ -285,7 +279,7 @@ namespace IRaCIS.Api.Controllers
var isExpire = _tokenService.IsTokenExpired(token);
if ( Guid.TryParse(userId,out _) == false || isExpire || !await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) )
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"))} ";
}
@@ -2,7 +2,6 @@
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Contracts.DTO;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Image.QA;
using IRaCIS.Core.Application.Service;
@@ -26,7 +25,6 @@ namespace IRaCIS.Core.API.Controllers
ITrialDocumentService _trialDocumentService,
IReadingImageTaskService _iReadingImageTaskService,
ITrialConfigService _trialConfigService,
IStudyService _studyService,
IClinicalAnswerService _clinicalAnswerService,
IReadingClinicalDataService _readingClinicalDataService,
IQCOperationService _qCOperationService,
@@ -36,31 +34,6 @@ namespace IRaCIS.Core.API.Controllers
) : ControllerBase
{
[HttpPost, Route("Inspection/NoneDicomStudy/UpdateNoneDicomStudy")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork]
public async Task<IResponseOutput> UpdateNoneDicomStudy(DataInspectionDto<NoneDicomEdit> opt, [FromServices] INoneDicomStudyService _noneDicomStudyService)
{
var singId = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _noneDicomStudyService.UpdateNoneDicomStudy(opt.Data);
await _inspectionService.CompletedSign(singId, result);
return result;
}
[HttpPost, Route("Inspection/QCOperation/UpdateDicomStudyInfo")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork]
public async Task<IResponseOutput> UpdateDicomStudyInfo(DataInspectionDto<DicomStudyEdit> opt, [FromServices] IQCOperationService _qcOperationService)
{
var singId = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _qcOperationService.UpdateDicomStudyInfo(opt.Data);
await _inspectionService.CompletedSign(singId, result);
return result;
}
#region
/// <summary>
/// 获取稽查数据
@@ -79,7 +52,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SetOncologyReadingInfo(DataInspectionDto<SubmitOncologyReadingInfoInDto> opt)
@@ -96,7 +69,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SubmitDicomVisitTask(DataInspectionDto<SubmitDicomVisitTaskInDto> opt)
@@ -115,7 +88,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SubmitGlobalReadingInfo(DataInspectionDto<SubmitGlobalReadingInfoInDto> opt)
@@ -134,7 +107,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> TrialReadingInfoSign(DataInspectionDto<TrialReadingInfoSignInDto> opt)
@@ -147,48 +120,19 @@ namespace IRaCIS.Core.API.Controllers
}
/// <summary>
/// 修正患者基本信息
/// </summary>
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/Study/AmendmentPatientInfo")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork]
public async Task<IResponseOutput> AmendmentPatientInfo(DataInspectionDto<EditPatientInfoCommand> opt)
{
var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _studyService.AmendmentPatientInfo(opt.Data);
await _inspectionService.CompletedSign(singid, result);
return result;
}
/// <summary>
/// 医学审核完成
/// </summary>
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<CloseAndFinishMedicalReview> opt)
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
{
var singid = await _inspectionService.RecordSing(opt.SignInfo);
await _readingMedicalReviewService.ClosedMedicalReviewDialog(new ClosedMedicalReviewDialogInDto() {
DialogCloseReason=opt.Data.DialogCloseReason,
IsClosedDialog=opt.Data.IsClosedDialog,
MedicalDialogCloseEnum=opt.Data.MedicalDialogCloseEnum,
TaskMedicalReviewId=opt.Data.TaskMedicalReviewId
});
var result = await _readingMedicalReviewService.FinishMedicalReview(new FinishMedicalReviewInDto() {
TaskMedicalReviewId=opt.Data.TaskMedicalReviewId
});
var result = await _readingMedicalReviewService.FinishMedicalReview(opt.Data);
await _inspectionService.CompletedSign(singid, result);
return result;
}
@@ -199,7 +143,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> ConfirmReadingMedicineQuestion(DataInspectionDto<ConfirmReadingMedicineQuestionInDto> opt)
@@ -217,7 +161,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SubmitVisitTaskQuestions(DataInspectionDto<SubmitVisitTaskQuestionsInDto> opt)
@@ -235,7 +179,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCSignClinicalData")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> CRCSignClinicalData(DataInspectionDto<CRCSignClinicalDataInDto> opt)
@@ -253,7 +197,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCConfirmClinical")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> CRCConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
@@ -270,7 +214,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCCancelConfirmClinical")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> CRCCancelConfirmClinical(DataInspectionDto<CRCCancelConfirmClinicalInDto> opt)
@@ -288,7 +232,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/PMConfirmClinical")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> PMConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
@@ -306,7 +250,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingClinicalData/SignConsistencyAnalysisReadingClinicalData")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SignConsistencyAnalysisReadingClinicalData(DataInspectionDto<SignConsistencyAnalysisReadingClinicalDataInDto> opt)
@@ -323,7 +267,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/SubmitClinicalFormAndSign")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SubmitClinicalFormAndSign(DataInspectionDto<SubmitClinicalFormInDto> opt)
@@ -340,7 +284,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SubmitJudgeVisitTaskResult(DataInspectionDto<SaveJudgeVisitTaskResult> opt)
@@ -359,7 +303,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")]
[UnitOfWork]
[TrialGlobalLimit("BeforeOngoingCantOpt")]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
public async Task<IResponseOutput> ConfigTrialBasicInfoConfirm(DataInspectionDto<BasicTrialConfig> opt)
{
@@ -401,7 +345,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")]
[UnitOfWork]
[TrialGlobalLimit("BeforeOngoingCantOpt")]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
public async Task<IResponseOutput> ConfigTrialUrgentInfoConfirm(DataInspectionDto<TrialUrgentConfig> opt)
{
opt.Data.IsTrialUrgentConfirmed = true;
@@ -414,7 +358,7 @@ namespace IRaCIS.Core.API.Controllers
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")]
[UnitOfWork]
[TrialGlobalLimit("BeforeOngoingCantOpt")]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt)
{
opt.Data.IsTrialPACSConfirmed = true;
@@ -430,7 +374,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")]
[UnitOfWork]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
public async Task<IResponseOutput> TrialConfigSignatureConfirm(DataInspectionDto<SignConfirmDTO> opt)
{
@@ -447,7 +391,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")]
[UnitOfWork]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt)
{
@@ -465,7 +409,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> CRCRequestToQC(DataInspectionDto<CRCRequestToQCCommand> opt)
{
@@ -480,31 +424,21 @@ namespace IRaCIS.Core.API.Controllers
/// 设置QC 通过或者不通过 7:QC failed 8QC passed
/// </summary>
[HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> QCPassedOrFailed(DataInspectionDto<QCPassedOrFailedDto> opt)
{
var singid = await _inspectionService.RecordSing(opt.SignInfo);
if (opt.Data.IsSecondPass != null)
{
var result = await _qCOperationService.QCSecondReviewPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, (bool)opt.Data.IsSecondPass);
await _inspectionService.CompletedSign(singid, result);
return result;
}
else
{
var result = await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
await _inspectionService.CompletedSign(singid, result);
return result;
}
var result = await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
await _inspectionService.CompletedSign(singid, result);
return result;
}
/// <summary>
/// 一致性核查 回退 对话记录不清除 只允许PM回退
/// </summary>
[HttpPost, Route("Inspection/QCOperation/CheckBack")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> CheckBack(DataInspectionDto<IDDto> opt)
{
@@ -521,7 +455,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> ReadClinicalDataSign(DataInspectionDto<ReadingClinicalDataSignIndto> opt)
{
@@ -536,7 +470,7 @@ namespace IRaCIS.Core.API.Controllers
/// CRC 设置已经重传完成
/// </summary>
[HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt)
{
@@ -552,7 +486,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/TrialConfig/updateTrialState")]
[TrialGlobalLimit("BeforeOngoingCantOpt")]
[TrialGlobalLimit( "BeforeOngoingCantOpt")]
[UnitOfWork]
public async Task<IResponseOutput> UpdateTrialState(DataInspectionDto<UpdateTrialStateDto> opt)
{
@@ -568,7 +502,7 @@ namespace IRaCIS.Core.API.Controllers
/// </summary>
/// <returns></returns>
[HttpPost, Route("Inspection/TrialDocument/userConfirm")]
[TrialGlobalLimit("BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt")]
[TrialGlobalLimit( "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> UserConfirm(DataInspectionDto<UserConfirmCommand> opt)
{
@@ -585,10 +519,10 @@ namespace IRaCIS.Core.API.Controllers
/// </summary>
/// <returns></returns>
[HttpPost, Route("Inspection/VisitTask/ConfirmReReading")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt, [FromServices] IVisitTaskService _visitTaskService)
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt, [FromServices] IVisitTaskService _visitTaskService)
{
var singId = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _visitTaskService.ConfirmReReading(opt.Data);
@@ -9,7 +9,6 @@ using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
@@ -28,7 +27,6 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using MiniExcelLibs;
using Newtonsoft.Json;
@@ -40,7 +38,6 @@ using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Path = System.IO.Path;
@@ -274,7 +271,6 @@ namespace IRaCIS.Core.API.Controllers
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
public bool? IsImageSegmentLabel { get; set; }
public class OSSFileDTO
{
@@ -523,17 +519,7 @@ namespace IRaCIS.Core.API.Controllers
}
else
{
if (incommand.IsImageSegmentLabel == true)
{
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, ImageLabelNoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
}
else
{
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, NoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
}
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, NoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
}
@@ -541,7 +527,7 @@ namespace IRaCIS.Core.API.Controllers
}
var uploadFinishedTime = DateTime.Now;
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId, true);
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId,true);
noneDicomStudy.FileCount = noneDicomStudy.FileCount + (incommand.VisitTaskId != null ? 0 : incommand.UploadedFileList.Count);
@@ -569,7 +555,6 @@ namespace IRaCIS.Core.API.Controllers
/// 一致性核查 excel上传 支持三种格式
/// </summary>
/// <param name="trialId"></param>
/// <param name="isFullCheck"></param>
/// <param name="oSSService"></param>
/// <param name="_inspectionFileRepository"></param>
/// <returns></returns>
@@ -577,14 +562,12 @@ namespace IRaCIS.Core.API.Controllers
[HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, bool isFullCheck, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
{
var fileName = string.Empty;
var templateFileStream = new MemoryStream();
var inspectionFileId = Guid.Empty;
await FileUploadToOSSAsync(async (realFileName, fileStream) =>
{
fileName = realFileName;
@@ -599,11 +582,9 @@ namespace IRaCIS.Core.API.Controllers
templateFileStream.Seek(0, SeekOrigin.Begin);
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DataReconciliation });
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, "InspectionUpload/Check", realFileName);
var addEntity = await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
inspectionFileId = addEntity.Id;
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId });
return ossRelativePath;
@@ -749,11 +730,8 @@ namespace IRaCIS.Core.API.Controllers
if (etcCheckList == null || etcCheckList.Count == 0)
{
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
}
else
{
@@ -773,8 +751,6 @@ namespace IRaCIS.Core.API.Controllers
if (etcCheckList.Count == 0)
{
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
}
@@ -786,19 +762,8 @@ namespace IRaCIS.Core.API.Controllers
//var client = _mediator.CreateRequestClient<ConsistenCheckCommand>();
//await client.GetResponse<ConsistenCheckResult>(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
if (isFullCheck)
{
await _mediator.Send(new ConsistenFullCheckCommand() { ETCList = etcCheckList, TrialId = trialId, InspectionFileId = inspectionFileId });
}
else
{
//不获取结果,不用定义返回类型
await _mediator.Send(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
}
//不获取结果,不用定义返回类型
await _mediator.Send(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
return ResponseOutput.Ok();
@@ -834,13 +799,12 @@ namespace IRaCIS.Core.API.Controllers
[HttpPost, Route("TrialSiteSurvey/UploadTrialSiteSurveyUser")]
[DisableFormValueModelBinding]
[UnitOfWork]
public async Task<IResponseOutput> UploadTrialSiteSurveyUser(Guid trialId, string baseUrl, string routeUrl,
[FromServices] IRepository<TrialSite> _trialSiteRepository,
[FromServices] IRepository<UserType> _usertypeRepository,
[FromServices] ITrialSiteSurveyService _trialSiteSurveyService,
[FromServices] IOSSService oSSService,
[FromServices] IOptionsMonitor<SystemEmailSendConfig> _systemEmailConfig,
[FromServices] IRepository<InspectionFile> _inspectionFileRepository)
{
var templateFileStream = new MemoryStream();
@@ -857,9 +821,9 @@ namespace IRaCIS.Core.API.Controllers
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_TemplateUploadData"]);
}
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/SiteSurvey", realFileName, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.SiteUserSurvey });
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, "InspectionUpload/SiteSurvey", realFileName);
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId });
@@ -872,26 +836,6 @@ namespace IRaCIS.Core.API.Controllers
.Where(t => !(string.IsNullOrWhiteSpace(t.TrialSiteCode) && string.IsNullOrWhiteSpace(t.FirstName) && string.IsNullOrWhiteSpace(t.LastName) && string.IsNullOrWhiteSpace(t.Email)
&& string.IsNullOrWhiteSpace(t.Phone) && string.IsNullOrWhiteSpace(t.UserTypeStr) && string.IsNullOrWhiteSpace(t.OrganizationName))).ToList();
//处理前后空格
foreach (var excel in excelList)
{
excel.Email = excel.Email.Trim();
excel.Phone = excel.Phone.Trim();
excel.OrganizationName = excel.OrganizationName.Trim();
excel.UserTypeStr = excel.UserTypeStr?.Trim();
excel.TrialSiteCode = excel.TrialSiteCode.Trim();
excel.FirstName = excel.FirstName.Trim();
excel.LastName = excel.LastName.Trim();
}
var emailRegexStr = _systemEmailConfig.CurrentValue.EmailRegexStr;
if (excelList.Any(t => !Regex.IsMatch(t.Email, emailRegexStr)))
{
var errorList = excelList.Where(t => !Regex.IsMatch(t.Email, emailRegexStr)).Select(t => t.Email).ToList();
//有邮箱不符合邮箱格式,请核查Excel数据
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"] + string.Join(" | ", errorList));
}
if (excelList.Any(t => string.IsNullOrWhiteSpace(t.TrialSiteCode) || string.IsNullOrWhiteSpace(t.FirstName) || string.IsNullOrWhiteSpace(t.LastName) || string.IsNullOrWhiteSpace(t.Email) || string.IsNullOrWhiteSpace(t.UserTypeStr)))
{
//请确保Excel中 每一行的 中心编号,姓名,邮箱,用户类型数据记录完整再进行上传
@@ -920,6 +864,11 @@ namespace IRaCIS.Core.API.Controllers
}
}
if (excelList.Any(t => !t.Email.Contains("@")))
{
//有邮箱不符合邮箱格式,请核查Excel数据
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"]);
}
var generateUserTypeList = new List<string>() { "CRC", "CRA" };
//if (excelList.Any(t => !generateUserTypeList.Contains(t.UserTypeStr.ToUpper())))
@@ -933,7 +882,7 @@ namespace IRaCIS.Core.API.Controllers
}
//处理好 用户类型 和用户类型枚举
var sysUserTypeList = _usertypeRepository.Where(t => t.UserTypeEnum == UserTypeEnum.CRA || t.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).Select(t => new { UserTypeId = t.Id, t.UserTypeEnum }).ToList();
var siteList = _trialSiteRepository.Where(t => t.TrialId == trialId && siteCodeList.Contains(t.TrialSiteCode)).Select(t => new { t.TrialSiteCode, TrialSiteId = t.Id, t.Country }).ToList();
var siteList = _trialSiteRepository.Where(t => t.TrialId == trialId && siteCodeList.Contains(t.TrialSiteCode)).Select(t => new { t.TrialSiteCode, TrialSiteId = t.Id }).ToList();
foreach (var item in excelList)
{
@@ -955,8 +904,6 @@ namespace IRaCIS.Core.API.Controllers
}
item.TrialSiteId = siteList.FirstOrDefault(t => t.TrialSiteCode.Trim().ToUpper() == item.TrialSiteCode.Trim().ToUpper()).TrialSiteId;
item.Country = siteList.FirstOrDefault(t => t.TrialSiteCode.Trim().ToUpper() == item.TrialSiteCode.Trim().ToUpper()).Country;
}
var list = excelList.Where(t => t.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator || t.UserTypeEnum == UserTypeEnum.CRA).ToList();
@@ -1007,11 +954,7 @@ namespace IRaCIS.Core.API.Controllers
EmailBodyHtml = 4,
ReadKeyFile = 5,
Other = 6,
Other = 5
}
/// <summary>
@@ -1043,9 +986,6 @@ namespace IRaCIS.Core.API.Controllers
case UploadFileType.EmailBodyHtml:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.EmailTemplate, fileName));
break;
case UploadFileType.ReadKeyFile:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.ReadKetFile, fileName));
break;
default:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetOtherFileUploadPath(_hostEnvironment, StaticData.Folder.TempFile, fileName));
@@ -15,13 +15,12 @@ using Hangfire.Storage;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using MassTransit.Mediator;
using Microsoft.Extensions.Configuration;
namespace IRaCIS.Core.API.HostService;
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IConfiguration _config,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IMediator _mediator,
ILogger<HangfireHostService> _logger) : IHostedService
@@ -42,24 +41,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
HangfireJobHelper.RemoveCronJob(jobId);
}
// 清除所有可能存在的定时任务,防止类型加载错误
var allJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Select(t => t.Id).ToList();
foreach (var jobId in allJobIdList)
{
try
{
HangfireJobHelper.RemoveCronJob(jobId);
_logger.LogInformation($"已清除定时任务: {jobId}");
}
catch (Exception ex)
{
_logger.LogWarning($"清除定时任务 {jobId} 时出错: {ex.Message}");
}
}
string defaultCulture = _config.GetValue<string>("SystemEmailSendConfig:CronEmailDefaultCulture");
// 项目手动选择,周期性邮件
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.ToListAsync();
@@ -71,11 +53,11 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
var trialId = task.TrialId;
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
}
// 系统邮件定时任务 要设置默认语言
// 系统邮件定时任务
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
.ToListAsync();
@@ -85,7 +67,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
}
@@ -1,168 +0,0 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace IRaCIS.Core.API.HostService;
public class SyncFileRecoveryService(IServiceScopeFactory _scopeFactory, FileSyncQueue _fileSyncQueue) : BackgroundService
{
private readonly int _pageSize = 500;
/// <summary>
/// 多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//在本地调试的时候,不干涉部署同步任务
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
using var scope = _scopeFactory.CreateScope();
var fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
// 延迟启动,保证主机快速启动
await Task.Delay(5000, stoppingToken);
int page = 0;
while (!stoppingToken.IsCancellationRequested)
{
// 分页获取未入队任务
var pending = await fileUploadRecordRepository
.Where(x => x.IsNeedSync == true && (x.IsSync == false || x.IsSync == null))
.OrderByDescending(x => x.Priority)
.Select(t => new { t.Id, t.Priority })
.Skip(page * _pageSize)
.Take(_pageSize)
.ToListAsync(stoppingToken);
if (!pending.Any())
break; // 扫描完毕,退出循环
foreach (var file in pending)
{
//file.IsQueued = true; // 避免重复入队
_fileSyncQueue.Enqueue(file.Id, file.Priority ?? 0); // 放入队列
}
page++; // 下一页
await Task.Delay(200, stoppingToken); // 缓解数据库压力
}
}
}
public class FileSyncWorker(IServiceScopeFactory _scopeFactory, ILogger<FileSyncWorker> _logger, FileSyncQueue _fileSyncQueue) : BackgroundService
{
// ⭐ 自动根据服务器CPU
private readonly int _workerCount = Math.Max(1, Environment.ProcessorCount - 1);
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
for (int i = 0; i < _workerCount; i++)
Task.Run(() => WorkerLoop(stoppingToken));
return Task.CompletedTask;
}
private async Task WorkerLoop(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var id = await _fileSyncQueue.DequeueAsync(stoppingToken);
try
{
using var scope = _scopeFactory.CreateScope();
var _fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
var _uploadFileSyncRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UploadFileSyncRecord>>();
var syncConfig = (scope.ServiceProvider.GetRequiredService<IOptionsMonitor<ObjectStoreServiceOptions>>()).CurrentValue;
var oss = scope.ServiceProvider.GetRequiredService<IOSSService>();
var file = await _fileUploadRecordRepository.FirstOrDefaultAsync(t => t.Id == id);
// ✅ 不要 return
if (file == null || file.IsNeedSync != true || syncConfig.IsOpenStoreSync == false)
{
continue;
}
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
continue;
}
//如果发现系统配置某一边同步进行了关闭,那么就直接返回,不执行任务
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
return;
}
var log = new UploadFileSyncRecord
{
FileUploadRecordId = id,
StartTime = DateTime.Now,
JobState = jobState.RUNNING
};
await _uploadFileSyncRecordRepository.AddAsync(log);
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
try
{
await oss.SyncFileAsync(file.Path.TrimStart('/'), file.UploadRegion == "CN" ? ObjectStoreUse.AliyunOSS : ObjectStoreUse.AWS, file.UploadRegion == "CN" ? ObjectStoreUse.AWS : ObjectStoreUse.AliyunOSS);
file.IsSync = true;
file.SyncFinishedTime = DateTime.Now;
log.JobState = jobState.SUCCESS;
}
catch (Exception ex)
{
log.JobState = jobState.FAILED;
log.Msg = ex.Message.Length > 300 ? ex.Message.Substring(0, 300) : ex.Message;
}
log.EndTime = DateTime.Now;
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync failed {Id}", id);
}
finally
{
// ⭐⭐⭐ 永远执行
_fileSyncQueue.Complete(id);
}
}
}
}
+6 -5
View File
@@ -76,15 +76,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ConfigMapFileProvider" Version="2.0.1" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.20" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.20" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.18" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Email" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
<PackageReference Include="Serilog.Sinks.Email" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.1" />
</ItemGroup>
<ItemGroup>
+2 -17
View File
@@ -136,14 +136,7 @@
<param name="opt"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.AmendmentPatientInfo(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Contracts.EditPatientInfoCommand})">
<summary>
修正患者基本信息
</summary>
<param name="opt"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.FinishMedicalReview(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Service.Reading.Dto.CloseAndFinishMedicalReview})">
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.FinishMedicalReview(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Service.Reading.Dto.FinishMedicalReviewInDto})">
<summary>
医学审核完成
</summary>
@@ -325,12 +318,11 @@
<param name="_noneDicomStudyFileRepository"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,System.Boolean,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
<summary>
一致性核查 excel上传 支持三种格式
</summary>
<param name="trialId"></param>
<param name="isFullCheck"></param>
<param name="oSSService"></param>
<param name="_inspectionFileRepository"></param>
<returns></returns>
@@ -351,13 +343,6 @@
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.HostService.SyncFileRecoveryService.ExecuteAsync(System.Threading.CancellationToken)">
<summary>
多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
</summary>
<param name="stoppingToken"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.API.IpPolicyRateLimitSetup">
<summary>
IPLimit限流 启动服务
+2 -23
View File
@@ -1,8 +1,6 @@
using FellowOakDicom;
using IRaCIS.Core.API;
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;
@@ -15,7 +13,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -24,7 +21,6 @@ using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -32,10 +28,6 @@ using System.Runtime.InteropServices;
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
Console.WriteLine("Startup Culture: " + CultureInfo.CurrentCulture.Name);
Console.WriteLine("Startup UI Culture: " + CultureInfo.CurrentUICulture.Name);
#region
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
@@ -65,8 +57,7 @@ SerilogExtension.AddSerilogSetup(enviromentName);
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = enviromentName,
Args = args
EnvironmentName = enviromentName
});
#region
@@ -91,10 +82,6 @@ builder.Services.ConfigureServices(_configuration);
builder.Services.AddHostedService<HangfireHostService>();
builder.Services.AddSingleton<FileSyncQueue>();
builder.Services.AddHostedService<SyncFileRecoveryService>();
builder.Services.AddHostedService<FileSyncWorker>();
//minimal api 异常处理
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//builder.Services.AddProblemDetails();
@@ -108,18 +95,11 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
// 关键配置:禁用不可空引用类型的自动 Required 验证
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
// 插到最前,抢在默认绑定器之前
//options.ModelBinderProviders.Insert(0, new SpecificNullableBinderProvider());
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
options.Filters.Add<LimitUserRequestAuthorization>();
options.Filters.Add<TrialGlobalLimitActionFilter>();
options.Filters.Add<RequestDuplicationFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
@@ -180,7 +160,6 @@ var env = app.Environment;
#region
DicomSetupBuilder.UseServiceProvider(app.Services);
app.UseMiddleware<EncryptionRequestMiddleware>();
+10 -10
View File
@@ -4,7 +4,7 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://0.0.0.0:3305",
"applicationUrl": "http://localhost:3305",
"sslPort": 0
}
},
@@ -23,7 +23,7 @@
"ASPNETCORE_ENVIRONMENT": "Test_IRC",
"ASPNETCORE_OpenSwagger": "true"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.Test_IRC_PGSQL": {
"commandName": "Project",
@@ -31,15 +31,15 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test_IRC_PGSQL"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.Prod_Event_IRC": {
"IRaCIS.Event_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Prod_Event_IRC"
"ASPNETCORE_ENVIRONMENT": "Event_IRC"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"Docker": {
"commandName": "Docker",
@@ -53,7 +53,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Uat_IRC"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.Prod_IRC": {
"commandName": "Project",
@@ -61,7 +61,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Prod_IRC"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.US_Uat_IRC": {
"commandName": "Project",
@@ -69,7 +69,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.US_Prod_IRC": {
"commandName": "Project",
@@ -77,7 +77,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Prod_IRC"
},
"applicationUrl": "http://0.0.0.0:6100"
"applicationUrl": "http://localhost:6100"
}
}
-1
View File
@@ -1,3 +1,2 @@
{
}
@@ -39,8 +39,6 @@ namespace IRaCIS.Core.API._PipelineExtensions.Serilog
var requestBodyPayload = await ReadRequestBody(context.Request);
context.Items["RequestBody"] = requestBodyPayload;
using (LogContext.PushProperty("RequestBody", requestBodyPayload))
{
//await _next.Invoke()
@@ -8,21 +8,13 @@ namespace IRaCIS.Core.API
{
public static void AddDicomSetup(this IServiceCollection services)
{
// ⭐ 先做全局 DICOM 配置
new DicomSetupBuilder()
.SkipValidation() // 👈 在这里设置
.Build();
services.AddFellowOakDicom().AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>().AddImageManager<ImageSharpImageManager>();
// new DicomSetupBuilder()
// .RegisterServices(s => s.AddFellowOakDicom()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
// .AddImageManager<ImageSharpImageManager>()
// )
// .SkipValidation()
// .Build();
.RegisterServices(s => s.AddFellowOakDicom()
.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>()
)
.SkipValidation()
.Build();
}
}
}
@@ -40,12 +40,12 @@ namespace IRaCIS.Core.API
var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value;
if (!string.IsNullOrWhiteSpace(dbType) && dbType == "pgsql")
{
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure().CommandTimeout(90));
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
}
else
{
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure().CommandTimeout(90));
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
}
@@ -71,27 +71,29 @@ namespace IRaCIS.Core.API
DateTime dateTime;
// 2. 检查目标类型是否可空
bool isNullable = objectType == typeof(DateTime?);
var canConvert = DateTime.TryParse(reader.Value?.ToString(), out dateTime);
if (isNullable == false && canConvert == false)
if (reader.ValueType == typeof(DateTime) || reader.ValueType == typeof(DateTime?))
{
DateTime? nullableDateTime = reader.Value as DateTime?;
throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}");
if (nullableDateTime != null && nullableDateTime.HasValue)
{
dateTime = nullableDateTime.Value;
}
else
{
return null;
}
}
else
{
if (canConvert == false)
if (DateTime.TryParse((string)reader.Value, out dateTime) == false)
{
return null;
}
}
////如果前端传递带时区的,那么转换会报错,需要DateTimeKind.Unspecified
//dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
// 将客户端时间转换为服务器时区的时间
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
@@ -111,10 +113,7 @@ namespace IRaCIS.Core.API
//第一个参数默认使用系统本地时区 也就是应用服务器的时区
DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone);
//// 最简单的方式:创建 DateTimeOffset
//DateTimeOffset dateTimeOffset = new DateTimeOffset(clientZoneTime, _clientTimeZone.GetUtcOffset(clientZoneTime));
//writer.WriteValue(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:sszzz"));
//writer.WriteValue(clientZoneTime);
writer.WriteValue(clientZoneTime.ToString(_dateFormat));
}
@@ -127,51 +126,6 @@ namespace IRaCIS.Core.API
}
public class DateOnlyUniversalJsonConverter : JsonConverter
{
private readonly string _format;
public DateOnlyUniversalJsonConverter(string format = "yyyy-MM-dd")
{
_format = format;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateOnly) || objectType == typeof(DateOnly?);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteValue(""); // null -> 空字符串
return;
}
var date = (DateOnly)value;
if (date == default)
{
writer.WriteValue(""); // default(DateOnly) -> 空字符串
}
else
{
writer.WriteValue(date.ToString(_format));
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var str = reader.TokenType == JsonToken.Null ? null : reader.Value?.ToString();
if (string.IsNullOrWhiteSpace(str) || !DateOnly.TryParse(str, out var date))
{
return objectType == typeof(DateOnly?) ? null : default(DateOnly);
}
return date;
}
}
#region
@@ -49,8 +49,6 @@ namespace IRaCIS.Core.API
//必须放在后面
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
//options.SerializerSettings.Converters.Add(new DateOnlyUniversalJsonConverter("yyyy-MM-dd"));
})
.AddControllersAsServices()//动态webApi属性注入需要
@@ -3,10 +3,8 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
namespace IRaCIS.Core.API
{
@@ -53,90 +51,34 @@ namespace IRaCIS.Core.API
}
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _memberInfo;
private readonly bool _isNullable;
PropertyInfo _MemberInfo;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_memberInfo = memberInfo;
_MemberInfo = memberInfo;
}
// DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断
public object GetValue(object target)
{
var result = _memberInfo.GetValue(target);
// 检查类型是否为string或string?
if (_memberInfo.PropertyType == typeof(string) && result == null)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
//else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
#region string string? null ""
//// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的
//var isNullable1 = _memberInfo.CustomAttributes
// .Any(a => a.AttributeType.Name == "NullableAttribute");
////var isNullable2 = _memberInfo.CustomAttributes
//// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
//if (isNullable1)
//{
// return result;
//}
#endregion
// 如果是string类型,返回空字符串
return string.Empty;
}
return result;
}
//影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化
public void SetValue(object target, object value)
{
_memberInfo.SetValue(target, value);
#region string null
//if (_memberInfo.PropertyType == typeof(string))
//{
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
#region string string?
//////接收模型的时候 定义的明明是string 但是上面也有该属性,判断不准的 比如阅片跟踪列表查询
//var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute");
////不影响 string? 传递null 变为""
//if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false)
//{
// //如果不处理 前段传递null string不会接收,说前段没传递会验证报错
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
if (_MemberInfo.PropertyType == typeof(string))
{
//去掉前后空格
_MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/);
}
else
{
_MemberInfo.SetValue(target, value);
}
}
}
@@ -22,20 +22,16 @@ namespace IRaCIS.Core.API
var config = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("MassTransit", LogEventLevel.Warning)
//https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Logging.md
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.FromLogContext()
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:365);
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
#region
@@ -46,19 +42,19 @@ namespace IRaCIS.Core.API
var emailConfig = new SystemEmailSendConfig();
configuration.GetSection("SystemEmailSendConfig").Bind(emailConfig);
//if (emailConfig.IsOpenErrorNoticeEmail)
//{
// config.WriteTo.Email(options: new Serilog.Sinks.Email.EmailSinkOptions()
// {
// From = emailConfig.FromEmail,
// To = emailConfig.ErrorNoticeEmailList,
// Host = emailConfig.Host,
// Port = emailConfig.Port,
// Subject = new MessageTemplateTextFormatter("Log Alert - 系统发生了异常,请核查"),
// Credentials = new NetworkCredential(emailConfig.FromEmail, emailConfig.AuthorizationCode)
if (emailConfig.IsOpenErrorNoticeEmail)
{
config.WriteTo.Email(options: new Serilog.Sinks.Email.EmailSinkOptions()
{
From = emailConfig.FromEmail,
To = emailConfig.ErrorNoticeEmailList,
Host = emailConfig.Host,
Port = emailConfig.Port,
Subject = new MessageTemplateTextFormatter("Log Alert - 系统发生了异常,请核查"),
Credentials = new NetworkCredential(emailConfig.FromEmail, emailConfig.AuthorizationCode)
// }, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error);
//}
}, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error);
}
#endregion
Log.Logger = config.CreateLogger();
@@ -28,13 +28,12 @@ public static class ServiceCollectionSetup
services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
services.AddOptions().Configure<RequestDuplicationOptions>(_configuration.GetSection("RequestDuplicationOptions"));
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
//תͷ ȡʵIP
//转发头设置 获取真实IP
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
@@ -52,7 +51,7 @@ public static class ServiceCollectionSetup
services.AddScoped<IObtainTaskAutoCancelJob, ObtainTaskAutoCancelJob>();
// עService βķ
// 注册以Service 结尾的服务
services.Scan(scan => scan
.FromAssemblies(typeof(BaseService).Assembly)
.AddClasses(classes => classes.Where(t => t.Name.Contains("Service")))
@@ -71,23 +70,23 @@ public static class ServiceCollectionSetup
#endregion
// MediatR Ϣ ¼ ӳ עhandlerӦϵ
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
#region ʷ
#region
//builder.Services.AddMemoryCache();
////ϴ
////上传限制 配置
//builder.Services.Configure<FormOptions>(options =>
//{
// options.MultipartBodyLengthLimit = int.MaxValue;
// options.ValueCountLimit = int.MaxValue;
// options.ValueLengthLimit = int.MaxValue;
//});
//IP ð ߺ
//IP 限流 可设置白名单 或者黑名单
//services.AddIpPolicyRateLimitSetup(_configuration);
// û Ȩ
// 用户类型 策略授权
//services.AddAuthorizationPolicySetup(_configuration);
#endregion
}
@@ -95,25 +94,25 @@ public static class ServiceCollectionSetup
}
#region Autofac
#region Autofac
//public class AutofacModuleSetup : Autofac.Module
//{
// protected override void Load(ContainerBuilder containerBuilder)
// {
// #region byzhouhang 20210917 ˴עͲִ ԼDomain Infra.EFcore յIJִӿڶ ִļ
// #region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
// containerBuilder.RegisterGeneric(typeof(Repository<>))
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//עͲִ
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
// containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
// #endregion
// #region ָҲautofac ʵȡ https://www.cnblogs.com/xwhqwer/p/15320838.html
// #region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
// //ȡпͲʹע
// //获取所有控制器类型并使用属性注入
// containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
// .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
// .PropertiesAutowired();
@@ -129,7 +128,7 @@ public static class ServiceCollectionSetup
// //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
// //עhangfire ע
// //注册hangfire任务 依赖注入
// containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
// }
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@@ -116,9 +116,6 @@ public static class SwaggerSetup
//DocExpansion设置为none可折叠所有方法
options.DocExpansion(DocExpansion.None);
// 开启Swagger UI的搜索/过滤功能
options.EnableFilter();
//DefaultModelsExpandDepth设置为 - 1 可不显示models
options.DefaultModelsExpandDepth(-1);
});
@@ -27,8 +27,7 @@ namespace IRaCIS.Core.API
hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions()
{
SchemaName = "dbo",
}).UseRecommendedSerializerSettings();
// 移除 UseSimpleAssemblyNameTypeSerializer() 以避免类型加载问题
}).UseRecommendedSerializerSettings().UseSimpleAssemblyNameTypeSerializer();
}
@@ -0,0 +1,75 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"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"
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://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": "http://192.168.3.68",
"port": "8001",
"useSSL": false,
"accessKey": "IDFkwEpWej0b4DtiuThL",
"secretKey": "Lhuu83yMhVwu7c1SnjvGY6lq74jzpYqifK6Qtj4h",
"bucketName": "test"
}
},
"BasicSystemConfig": {
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 60,
"ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "uat@extimaging.com",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.event.extimaging.com/login",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false
}
}
@@ -1,125 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=101.132.193.237,1433;Database=Prod_IRC_pidr_restore;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.193.237,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,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"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": "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
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
"UseSSL": true,
"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,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120,
"OpenLoginMFA": false,
"ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "irc@extimaging.com",
"FromName": "IRC Imaging System",
"AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN"
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.193.237"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+17 -60
View File
@@ -7,39 +7,13 @@
}
},
"ConnectionStrings": {
//"RemoteNew": "Server=10.10.10.49,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=10.10.10.49,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
//"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;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,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"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",
@@ -52,21 +26,17 @@
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
"UseSSL": true,
"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
"MinIO": {
"endpoint": "http://192.168.3.68",
"port": "8001",
"useSSL": false,
"accessKey": "IDFkwEpWej0b4DtiuThL",
"secretKey": "Lhuu83yMhVwu7c1SnjvGY6lq74jzpYqifK6Qtj4h",
"bucketName": "test"
}
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -83,43 +53,30 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA
"UserMFAVerifyMinutes": 1440
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "irc@extimaging.com",
"FromName": "IRC Imaging System",
"FromName": "irc",
"AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN"
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.193.237"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+23 -134
View File
@@ -1,215 +1,104 @@
{
//
"Logging": {
//
"LogLevel": {
//
"Default": "Information",
//
"Microsoft": "Warning",
// ASP.NET Core
"Microsoft.Hosting.Lifetime": "Information"
}
},
//
"ConnectionStrings": {
//
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
// Hangfire
"Hangfire": "Server=106.14.89.110,1435;Database=Test_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
//
"ObjectStoreService": {
// 使
"ObjectStoreUse": "AliyunOSS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "CN",
"SyncConfigList": [
{
"Domain": "irc.test.extimaging.com",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.test.extimaging.com",
"Primary": "AWS",
"Target": "AliyunOSS",
"UploadRegion": "US",
"TargetRegion": "CN",
"IsOpenSync": true
}
],
//
"AliyunOSS": {
// OSS Region ID
"RegionId": "cn-shanghai",
// OSS 访
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
// OSS 访
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
// OSS 访 ID
"AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
// OSS 访 Secret
"AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
// OSS ARN
"RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
// OSS Bucket
"BucketName": "zy-irc-test-store",
// OSS 访
//"ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com",
"ViewEndpoint": "https://zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com",
// OSS
"Region": "oss-cn-shanghai",
// OSS 访
"DurationSeconds": 7200,
// OSS
"PreviewEndpoint": "https://test-oss.test.extimaging.com"
},
// MinIO
"MinIO": {
// MinIO 访
"EndPoint": "hir-oss.test.extimaging.com",
// MinIO
"Port": "443",
// 使 SSL
"UseSSL": true,
// MinIO ARN
"AccessKey": "fbStsVYCIPKHQneeqMwD",
// MinIO 访
"SecretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
// MinIO BucketName
"BucketName": "irc-test",
// MinIO 访
"ViewEndpoint": "https://hir-oss.test.extimaging.com/irc-test"
},
// AWS S3
"AWS": {
// AWS S3 Region
"Region": "us-east-1",
// AWS S3 访
"EndPoint": "s3.us-east-1.amazonaws.com",
// 使 SSL
"UseSSL": true,
// AWS S3 ARN
"RoleArn": "arn:aws:iam::471112624751:role/uat_s3_access",
// AWS S3 访 ID
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
// AWS S3 访 Secret
"SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
// AWS S3 Bucket
"BucketName": "ei-med-s3-lili-uat-store",
// AWS S3 访
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com",
// AWS S3
"DurationSeconds": 7200
}
},
//
"BasicSystemConfig": {
//
"QCRiskControl": true,
//
"OpenUserComplexPassword": false,
//
"OpenSignDocumentBeforeWork": false,
//
"OpenLoginLimit": false,
//
"LoginMaxFailCount": 5,
//
"LoginFailLockMinutes": 1,
//
"AutoLoginOutMinutes": 10,
// MFA
"OpenLoginMFA": false,
//
"ContinuousReadingTimeMin": 120,
//
"ReadingRestTimeMin": 10,
//
"IsNeedChangePassWord": true,
//
"ChangePassWordDays": 90,
// 1 ElevateLiLi 2 Extensive()
// 1 Elevate 2 Extensive
"TemplateType": 2,
//
"OpenTrialRelationDelete": true,
// PDF
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf",
//MFA
"UserMFAVerifyMinutes": 1440
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
},
//
"SystemEmailSendConfig": {
// SMTP
"Host": "smtp.qiye.aliyun.com",
// SMTP
"Port": 465,
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
//
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "test@extimaging.com",
//
"FromName": "Test IRC Imaging System",
// SMTP
"FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021",
// 访
"SiteUrl": "http://irc.test.extimaging.com/login",
// - 使
"SystemShortName": "IRC",
// -
"OrganizationName": "ExtImaging",
//
"OrganizationNameCN": "ExtImaging",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
//
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging",
//
"CompanyNameCN": "上海展影医疗科技有限公司",
//
"CompanyShortName": "Extensive Imaging",
//
"CompanyShortNameCN": "展影医疗",
// 便lili irc
"IsEnv_US": false,
//
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": ["872297557@qq.com"]
},
//
"CronEmailDefaultCulture": "zh-CN"
},
// PACS
"SystemPacsConfig": {
// PACS
"Port": "11113",
// PACSIP
"IP": "106.14.89.110"
},
//
"RequestDuplicationOptions": {
//
"IsEnabled": true,
//
"DuplicationWindowMs": 200,
//
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
@@ -47,8 +47,7 @@
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
@@ -76,15 +75,13 @@
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "test@extimaging.com",
"FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.test.extimaging.com/login",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -94,12 +91,5 @@
"SystemPacsConfig": {
"Port": "11113",
"IP": "106.14.89.110"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+15 -57
View File
@@ -12,46 +12,19 @@
//"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
//"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AWS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "US",
"SyncConfigList": [
{
"Domain": "eilili.elevateimaging.ai",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.elevateimaging.ai",
"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": "LTAI5tNRTsqL6aWmHkDmTwoH",
"AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH",
"RoleArn": "acs:ram::1899121822495495:role/irc-oss-access",
"BucketName": "zy-lili-store",
"ViewEndpoint": "https://zy-lili-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
"MinIO": {
"endPoint": "44.210.231.169",
"port": "9001",
"useSSL": false,
"accessKey": "e9bT1isTOqSAUxb6wd4n",
"secretKey": "b5TaDzNdQCBtCvfm8eZ3dR6yY7tfZu2JYze2Po1i",
"bucketName": "prod-irc-us",
"viewEndpoint": "http://44.210.231.169:9001/prod-irc-us/"
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
@@ -65,8 +38,7 @@
}
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -85,24 +57,17 @@
// 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenTrialRelationDelete": false,
//MFA
"UserMFAVerifyMinutes": 1440
"OpenTrialRelationDelete": false
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -112,20 +77,13 @@
"CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.elevateimaging.ai/login",
"IsEnv_US": true,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US"
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "44.210.231.169"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+6 -20
View File
@@ -44,8 +44,7 @@
}
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -67,24 +66,16 @@
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenLoginMFA": true,
//MFA
"UserMFAVerifyMinutes": 1440
"OpenLoginMFA": true
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -93,19 +84,14 @@
"CompanyShortName": "Elevate Imaging",
"CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.test.elevateimaging.ai/login",
"IsEnv_US": true
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+6 -25
View File
@@ -13,12 +13,6 @@
"RemoteNew": "Server=3.226.182.187,1435;Database=US_Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=3.226.182.187,1435;Database=US_Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
@@ -54,8 +48,7 @@
}
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -72,22 +65,16 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 1,
//MFA
"UserMFAVerifyMinutes": 1440
"TemplateType": 1
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -97,19 +84,13 @@
"CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.uat.elevateimaging.ai/login",
"IsEnv_US": true,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US"
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+10 -50
View File
@@ -10,35 +10,9 @@
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "CN",
"SyncConfigList": [
{
"Domain": "irc.uat.extimaging.com",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.uat.extimaging.com",
"Primary": "AWS",
"Target": "AliyunOSS",
"UploadRegion": "US",
"TargetRegion": "CN",
"IsOpenSync": true
}
],
"AliyunOSS": {
"RegionId": "cn-shanghai",
@@ -70,9 +44,9 @@
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
"UseSSL": true,
"RoleArn": "arn:aws:iam::471112624751:role/uat_s3_access",
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
"SecretAccessKey": "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",
"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/",
"DurationSeconds": 7200
@@ -80,8 +54,7 @@
},
"BasicSystemConfig": {
//
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -99,45 +72,32 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA
"UserMFAVerifyMinutes": 1440
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN"
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.253.119"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+1 -8
View File
@@ -3,7 +3,7 @@
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
"Issuer": "Extimaging",
"Audience": "EICS",
"TokenExpireMinute": "10080" //7
"TokenExpireMinute": "10080"//7
},
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
@@ -74,12 +74,5 @@
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
"scope": "repo"
}
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+3 -17
View File
@@ -1,9 +1,10 @@
<!-- HTML for static distribution bundle build -->
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>%(DocumentTitle)</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
@@ -112,23 +113,8 @@
}, info: function () {
return null;
}
},
fn: {
opsFilter: function (taggedOps, phrase) {
var normalPhrase = phrase.toLowerCase();
return taggedOps.map(function (tagObj, tag) {
var operations = tagObj.get("operations").filter(function (op) {
var summary = op.getIn(["operation", "summary"]) || "";
var path = op.get("path") || "";
var tagMatch = tag.toLowerCase().indexOf(normalPhrase) !== -1;
return tagMatch || summary.toLowerCase().indexOf(normalPhrase) !== -1 || path.toLowerCase().indexOf(normalPhrase) !== -1;
});
return tagObj.set("operations", operations);
}).filter(function (tagObj) {
return tagObj.get("operations").size > 0;
});
}
}
}
}
];
@@ -505,8 +505,8 @@ var abp = abp || {};
//Inputs
createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'user1wj');
createInput(modalUxContent, 'password', 'Password', 'password', '1');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'cyldev');
createInput(modalUxContent, 'password', 'Password', 'password', '123456');
createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', '');
createSelect(modalUxContent, 'roleSelect', 'role', [])
+1 -2
View File
@@ -39,8 +39,7 @@ namespace IRaCIS.Core.Application.Auth
new Claim(JwtIRaCISClaimType.UserTypeShortName,user.UserTypeShortName),
new Claim(JwtIRaCISClaimType.PermissionStr,user.PermissionStr),
new Claim(JwtIRaCISClaimType.IsZhiZhun,user.IsZhiZhun.ToString()),
new Claim(JwtIRaCISClaimType.IsTestUser,user.IsTestUser.ToString()),
new Claim(JwtIRaCISClaimType.UserWorkLanguage,user.UserWorkLanguage.ToString())
new Claim(JwtIRaCISClaimType.IsTestUser,user.IsTestUser.ToString())
};
////创建令牌
@@ -23,7 +23,5 @@ namespace IRaCIS.Core.Application.Auth
public bool IsZhiZhun { get; set; }
public string UserTypeShortName { get; set; } = string.Empty;
public UserWorkLanguage UserWorkLanguage { get; set; }
}
}
@@ -1,22 +1,14 @@
using IRaCIS.Core.Application.Helper.OtherTool;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
using System;
using System.Reflection;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace IRaCIS.Core.Application.Filter;
public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : ActionFilterAttribute, IActionFilter
public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter
{
@@ -31,55 +23,8 @@ public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _conf
.Select(e => e.ErrorMessage)
.ToArray();
var request = context.HttpContext.Request;
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:VueNoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = _userInfo.UserName.IsNotNullOrEmpty()? $"{_userInfo.UserName}({_userInfo.UserTypeShortName})": "匿名",
Api = $"{request.Method} {request.Path}",
Message = $"前端传递参数,后端模型验证失败\n 具体信息:{JsonConvert.SerializeObject(validationErrors)}",
JsonData = requestBody,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"模型验证过滤器里发送企业微信出现错误:{ex.Message}");
}
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject(validationErrors)));
}
}
}
@@ -1,16 +1,13 @@
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.Filter;
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : Attribute, IExceptionFilter
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
@@ -49,44 +46,7 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
: (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
var alert = new WeComAlert
{
Env = baseUrl,
UserName = $"{_userInfo.UserName}({_userInfo.UserTypeShortName})",
Api = context.HttpContext.Request.Path,
Message = exception.Message,
JsonData = requestBody,
Stack = exception.StackTrace,
AtUsers = userList ?? []
};
_ = WeComNotifier.SendAlertAsync(webhook, alert);
}
}
catch (Exception ex)
{
_logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}");
}
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
}
@@ -98,7 +58,6 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
_logger.LogError(errorInfo);
//_logger.LogError(exception, exception.Message);
}
else
@@ -1,140 +0,0 @@
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.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
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
{
/// <summary>
/// 请求拦截 请求前后的操作
/// </summary>
/// <param name="logger">logger</param>
/// <param name="accessor">loggerHelper</param>
public class RequestDuplicationFilter(ILogger<RequestDuplicationFilter> logger, IHttpContextAccessor accessor, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<RequestDuplicationOptions> RequestDuplicationOptionsMonitor) : ExceptionFilterAttribute, IAsyncActionFilter
{
/// <summary>
/// 传入的参数
/// </summary>
public string Intoparam { get; set; }
/// <summary>
/// 这个是正常记录(请求刚进入的时候)
/// </summary>
/// <param name="context">context</param>
/// <param name="next">next</param>
/// <returns>返回的对象</returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled)
{
Dictionary<string, object?> dic = new Dictionary<string, object?>();
var desc = context.ActionDescriptor as ControllerActionDescriptor;
foreach (var p in desc.Parameters)
{
// 关键判断:绑定源是否是 Services
if (p.BindingInfo?.BindingSource == BindingSource.Services) continue;
// 普通参数,取值
if (context.ActionArguments.TryGetValue(p.Name, out var value) && value != null)
{
dic.Add(p.Name, value);
}
}
this.Intoparam = JsonConvert.SerializeObject(dic);
try
{
this.RequestDuplication();
}
catch (BusinessValidationFailedException)
{
throw;
}
catch (Exception)
{
}
}
var resultContext = await next();
}
/// <summary>
///
/// </summary>
private void RequestDuplication()
{
var requestPath = accessor?.HttpContext?.Request?.Path.ToString() ?? string.Empty;
// 验证请求频繁情况
if (
requestPath == "/ReadingImageTask/changeDicomReadingQuestionAnswer"&&
!requestPath
.Split("/", StringSplitOptions.RemoveEmptyEntries)
.Any(segment => segment.StartsWith("get", StringComparison.OrdinalIgnoreCase)) &&
_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
};
var isAdded = IRCSystemInfo.TryAddRequestRecord(
requestInfo,
RequestDuplicationOptionsMonitor.CurrentValue.CacheTimeSeconds,
RequestDuplicationOptionsMonitor.CurrentValue.DuplicationWindowMs);
if (!isAdded)
{
throw new BusinessValidationFailedException(_localizer["RequestDuplicationFilter_RequestDuplication"], ApiResponseCodeEnum.BusinessValidationFailed);
}
}
}
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);
}
}
}
}
@@ -29,11 +29,11 @@ public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _
var requestHost = context.HttpContext.Request.Host;
// 检查请求是否来自 localhost:6100
if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
{
await next();
return;
}
//if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
//{
// await next();
// return;
//}
#region
// 用户类型检查
@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IRaCIS.Core.Application.Service.BusinessFilter;
@@ -7,7 +7,6 @@ namespace IRaCIS.Core.Domain.Share;
[Description("多环境 配置环境实体")]
public class ServiceVerifyConfigOption
{
public bool QCRiskControl { get; set; }
public bool OpenUserComplexPassword { get; set; }
public bool OpenSignDocumentBeforeWork { get; set; }
@@ -39,8 +38,6 @@ public class ServiceVerifyConfigOption
public string ThirdPdfUrl { get; set; }
public int UserMFAVerifyMinutes { get; set; } = 1440;
}
public class SystemEmailSendConfig
@@ -48,10 +45,6 @@ public class SystemEmailSendConfig
public int Port { get; set; }
public string Host { get; set; } = string.Empty;
public string Imap { get; set; } = string.Empty;
public int ImapPort { get; set; }
public string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
@@ -60,11 +53,7 @@ public class SystemEmailSendConfig
public string SiteUrl { get; set; } = string.Empty;
public string PlatformName { get; set; } = string.Empty;
public string PlatformNameCN { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string OrganizationName { get; set; } = string.Empty;
public string OrganizationNameCN { get; set; } = string.Empty;
@@ -79,17 +68,14 @@ public class SystemEmailSendConfig
public bool IsEnv_US { get; set; }
public string EmailRegexStr { get; set; }
public bool IsOpenErrorNoticeEmail { get; set; }
//public bool IsOpenErrorNoticeEmail { get; set; }
//public List<string> ErrorNoticeEmailList { get; set; } = new List<string>();
public List<string> ErrorNoticeEmailList { get; set; } =new List<string>();
}
public class SystemEmailSendConfigView
{
public string SystemShortName { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string CompanyName { get; set; } = string.Empty;
public string CompanyNameCN { get; set; } = string.Empty;
@@ -97,8 +83,6 @@ public class SystemEmailSendConfigView
public string CompanyShortName { get; set; } = string.Empty;
public string CompanyShortNameCN { get; set; } = string.Empty;
public string EmailRegexStr { get; set; }
}
public class SystemPacsConfig
@@ -117,79 +101,6 @@ public class IRCEncreptOption
public List<string> ApiPathList { get; set; }
}
/// <summary>
/// 请求缓存配置
/// </summary>
public class RequestDuplicationOptions
{
/// <summary>
/// 缓存时间(秒),默认5秒
/// </summary>
public int CacheTimeSeconds { get; set; } = 5;
/// <summary>
/// 重复请求检测时间窗口(毫秒),默认500毫秒
/// </summary>
public int DuplicationWindowMs { get; set; } = 500;
/// <summary>
/// 是否启用防重复请求
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 需要排除的路径(不进行重复检测)
/// </summary>
public List<string> ExcludePaths { get; set; } = new List<string>();
}
public static class IRCSystemInfo
{
public static List<RequestInfo> RequestRecordList { get; set; } = new List<RequestInfo>();
private static readonly object requestRecordLock = new object();
public static bool TryAddRequestRecord(RequestInfo requestInfo, int cacheTimeSeconds, int duplicationWindowMs)
{
lock (requestRecordLock)
{
var now = DateTime.Now;
RequestRecordList = RequestRecordList
.Where(x => x.RequestTime >= now.AddSeconds(-cacheTimeSeconds))
.ToList();
var isDuplicate = RequestRecordList.Any(x =>
x.RequestTime >= requestInfo.RequestTime.AddMilliseconds(-duplicationWindowMs) &&
x.RequestKey == requestInfo.RequestKey);
if (isDuplicate)
{
return false;
}
RequestRecordList.Add(requestInfo);
return true;
}
}
}
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; }
/// <summary>
/// 请求的唯一标识
/// </summary>
public string RequestKey => $"{UserRoleId}_{RequestPath}_{ParameterHash}";
}
public class IRaCISBasicConfigOption
{
public string DoctorCodePrefix { get; set; }
@@ -261,4 +172,4 @@ public static class AppSettings
}
}
}
-1
View File
@@ -11,6 +11,5 @@ global using AutoMapper;
global using IRaCIS.Core.Domain.Share;
global using IRaCIS.Core.Application.BusinessFilter;
global using IdentityUser = IRaCIS.Core.Domain.Models.IdentityUser;
global using Serilog;
+1 -15
View File
@@ -1,6 +1,4 @@
using DocumentFormat.OpenXml.Spreadsheet;
namespace IRaCIS.Core.Application.Helper;
namespace IRaCIS.Core.Application.Helper;
public static class CacheKeys
@@ -63,15 +61,6 @@ public static class CacheKeys
/// <returns></returns>
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId,string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string UserMFATag(Guid userId) => $"UserMFAVerifyPass:{userId}";
public static string TrialDataStoreType(Guid trialId) => $"TrialDataStoreType:{trialId}";
public static string UserQCSkipTask(Guid userId) => $"UserSKipQCTask:{userId}";
}
public static class CacheHelper
@@ -83,9 +72,6 @@ public static class CacheHelper
return statusStr;
}
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
{
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
@@ -1,289 +0,0 @@
using DocumentFormat.OpenXml.Office.CustomUI;
using FellowOakDicom;
using FellowOakDicom.Media;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using NPOI.Util;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Helper
{
public class StudyDIRInfo
{
public Guid SubjectId { get; set; }
public bool IsTaskStudy { get; set; }
public Guid SubjectVisitId { get; set; }
// Study
public Guid DicomStudyId { get; set; }
public string PatientId { get; set; }
public string PatientName { get; set; }
public string PatientBirthDate { get; set; }
public string PatientSex { get; set; }
public string StudyInstanceUid { get; set; }
public string StudyId { get; set; }
public string DicomStudyDate { get; set; }
public string DicomStudyTime { get; set; }
public string AccessionNumber { get; set; }
public string StudyDescription { get; set; }
// Series
public string SeriesInstanceUid { get; set; }
public string Modality { get; set; }
public string DicomSeriesDate { get; set; }
public string DicomSeriesTime { get; set; }
public int SeriesNumber { get; set; }
public string SeriesDescription { get; set; }
// Instance
public Guid InstanceId { get; set; }
public string SopInstanceUid { get; set; }
public string SOPClassUID { get; set; }
public int InstanceNumber { get; set; }
public string MediaStorageSOPClassUID { get; set; }
public string MediaStorageSOPInstanceUID { get; set; }
public string TransferSytaxUID { get; set; }
}
public static class DicomDIRHelper
{
public static async Task GenerateStudyDIRAndUploadAsync(List<StudyDIRInfo> list, Dictionary<string, string> dic, string ossFolder, IOSSService _oSSService)
{
var mappings = new List<string>();
int index = 1;
var trialId = Guid.Empty;
Guid.TryParse(ossFolder.Split('/', StringSplitOptions.RemoveEmptyEntries)[0], out trialId);
var studyUid = list.FirstOrDefault()?.StudyInstanceUid;
var dicomDir = new DicomDirectory();
foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber))
{
var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID);
if (dicomUid != null)
{
var ts = DicomTransferSyntax.Query(dicomUid);
var dataset = new DicomDataset(ts)
{
{ DicomTag.PatientID, item.PatientId ?? string.Empty },
{ DicomTag.PatientName, item.PatientName ?? string.Empty },
{ DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
{ DicomTag.PatientSex, item.PatientSex ?? string.Empty },
{ DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty },
{ DicomTag.StudyID, item.StudyId ?? string.Empty },
{ DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty },
{ DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty },
{ DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty },
{ DicomTag.StudyDescription, item.StudyDescription ?? string.Empty },
{ DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty },
{ DicomTag.Modality, item.Modality ?? string.Empty },
{ DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty },
{ DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty },
{ DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty },
{ DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty },
{ DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty },
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty },
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty },
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty },
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty },
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty },
};
var dicomFile = new DicomFile(dataset);
// 文件名递增格式:IM_00001, IM_00002, ...
string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last());
dicomDir.AddFile(dicomFile, filename);
index++;
}
}
//有实际的文件
if (mappings.Count > 0)
{
#region
var tempFilePath = Path.GetTempFileName();
// 保存 DICOMDIR 到临时文件 不能直接写入到流种
await dicomDir.SaveAsync(tempFilePath);
using (var memoryStream = new MemoryStream(File.ReadAllBytes(tempFilePath)))
{
// 重置流位置
memoryStream.Position = 0;
var relativePath = await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DICOMDIR });
dic.Add($"{studyUid}_DICOMDIR", relativePath.Split('/').Last());
}
//清理临时文件
File.Delete(tempFilePath);
#endregion
#region
// 将映射写入内存流
var mappingText = string.Join(Environment.NewLine, mappings);
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DICOMDIR });
#endregion
}
}
public static async Task GenerateStudyDIR(List<StudyDIRInfo> list, Dictionary<string, string> dic, string? dirSavePath = null, Stream? outputStream = null)
{
var mappings = new List<string>();
int index = 1;
var trialId = Guid.Empty;
var studyUid = list.FirstOrDefault()?.StudyInstanceUid;
var dicomDir = new DicomDirectory();
foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber))
{
var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID);
if (dicomUid != null)
{
var ts = DicomTransferSyntax.Query(dicomUid);
var dataset = new DicomDataset(ts)
{
{ DicomTag.PatientID, item.PatientId ?? string.Empty },
{ DicomTag.PatientName, item.PatientName ?? string.Empty },
{ DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
{ DicomTag.PatientSex, item.PatientSex ?? string.Empty },
{ DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty },
{ DicomTag.StudyID, item.StudyId ?? string.Empty },
{ DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty },
{ DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty },
{ DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty },
{ DicomTag.StudyDescription, item.StudyDescription ?? string.Empty },
{ DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty },
{ DicomTag.Modality, item.Modality ?? string.Empty },
{ DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty },
{ DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty },
{ DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty },
{ DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty },
{ DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty },
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty },
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty },
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty },
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty },
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty },
};
var dicomFile = new DicomFile(dataset);
// 文件名递增格式:IM_00001, IM_00002, ...
string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last());
dicomDir.AddFile(dicomFile, filename);
index++;
}
}
//有实际的文件
if (mappings.Count > 0)
{
// 保存 DICOMDIR 到临时文件 不能直接写入到流种
if (dirSavePath.IsNotNullOrEmpty())
{
await dicomDir.SaveAsync(dirSavePath);
}
else
{
await dicomDir.SaveAsync(outputStream);
}
}
}
public static StudyDIRInfo ReadDicomDIRInfo(DicomFile dicomFile)
{
var dataset = dicomFile.Dataset;
var info = new StudyDIRInfo
{
PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
StudyInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty),
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
StudyDescription = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
SeriesInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
SeriesDescription = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SopInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty),
SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
MediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
MediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
TransferSytaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty)
};
return info;
}
}
}
@@ -1,7 +1,4 @@
using System.Globalization;
using System.Text.RegularExpressions;
namespace IRaCIS.Core.Application.Helper;
namespace IRaCIS.Core.Application.Helper;
public static class IRCEmailPasswordHelper
{
@@ -77,56 +74,4 @@ public static class IRCEmailPasswordHelper
// 随机打乱密码字符顺序
return new string(password.OrderBy(_ => Random.Next()).ToArray());
}
// https://learn.microsoft.com/zh-cn/dotnet/standard/base-types/how-to-verify-that-strings-are-in-valid-email-format
/// <summary>
/// 微软官方邮件验证 很宽松
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
public static bool IsValidEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
try
{
// Normalize the domain
email = Regex.Replace(email, @"(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200));
// Examines the domain part of the email and normalizes it.
string DomainMapper(Match match)
{
// Use IdnMapping class to convert Unicode domain names.
var idn = new IdnMapping();
// Pull out and process domain name (throws ArgumentException on invalid)
string domainName = idn.GetAscii(match.Groups[2].Value);
return match.Groups[1].Value + domainName;
}
}
catch (RegexMatchTimeoutException e)
{
return false;
}
catch (ArgumentException e)
{
return false;
}
try
{
return Regex.IsMatch(email,
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
}
catch (RegexMatchTimeoutException)
{
return false;
}
}
}
@@ -2,7 +2,6 @@
using MailKit;
using MailKit.Security;
using MimeKit;
using Org.BouncyCastle.Tls;
namespace IRaCIS.Core.Application.Helper;
@@ -10,15 +9,12 @@ namespace IRaCIS.Core.Application.Helper;
public static class SendEmailHelper
{
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return string.Empty;
{
return;
}
try
@@ -46,7 +42,6 @@ public static class SendEmailHelper
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
@@ -56,133 +51,9 @@ public static class SendEmailHelper
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
/// <summary>
/// 发送项目邮件
/// </summary>
/// <param name="messageToSend"></param>
/// <param name="trial"></param>
/// <param name="messageSentSuccess"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<string> SendTrialEmailAsync(MimeMessage messageToSend, Trial trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
// 项目的需要重设 发件地址与邮件地址
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
if (fromAddress != null)
{
messageToSend.From.Clear();
messageToSend.From.Add(new MailboxAddress(trial.EmailFromName, trial.EmailFromEmail));
}
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return string.Empty;
}
// 替换邮件标题
if (!string.IsNullOrEmpty(messageToSend.Subject))
{
foreach (var item in trial.TrialObjectNameList)
{
// 把标题里的占位符替换成真实名称
messageToSend.Subject = messageToSend.Subject.Replace(item.Name, item.TrialName);
}
}
// 构建替换字典
var replacements = new Dictionary<string, string>();
foreach (var item in trial.TrialObjectNameList)
{
replacements[item.Name] = item.TrialName;
}
// 安全替换 HTML
ReplaceHtmlContent(messageToSend.Body, replacements);
try
{
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
{
if (messageSentSuccess != null)
{
smtp.MessageSent += messageSentSuccess;
}
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//await smtp.ConnectAsync("smtp.qq.com", 465, SecureSocketOptions.SslOnConnect);
//await smtp.AuthenticateAsync("zhou941003@qq.com", "sqfhlpfdvnexbcab");
//await smtp.ConnectAsync(_systemEmailConfig.Host, _systemEmailConfig.Port, SecureSocketOptions.Auto);
//await smtp.AuthenticateAsync(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
await smtp.ConnectAsync(trial.EmailSMTPServerAddress, trial.EmailSMTPServerPort, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.SendAsync(messageToSend);
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
{
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
// 遍历邮件体,找到 HTML 部分并替换
public static void ReplaceHtmlContent(MimeEntity entity, Dictionary<string, string> replacements)
{
if (entity is Multipart multipart)
{
foreach (var part in multipart)
{
ReplaceHtmlContent(part, replacements);
}
}
else if (entity is TextPart textPart && textPart.IsHtml)
{
// 只有这里才是真正的 HTML
foreach (var kv in replacements)
{
textPart.Text = textPart.Text.Replace(kv.Key, kv.Value);
}
}
}
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
@@ -202,7 +73,7 @@ public static class SendEmailHelper
return true;
}
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, Trial? trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
var messageToSend = new MimeMessage();
@@ -216,9 +87,8 @@ public static class SendEmailHelper
if (sMTPEmailConfig.ToMailAddressList.Count == 0)
{
return;
//---没有收件人
//throw new ArgumentException(I18n.T("SendEmail_NoRecipient"));
throw new ArgumentException(I18n.T("SendEmail_NoRecipient"));
}
else
{
@@ -273,7 +143,7 @@ public static class SendEmailHelper
await smtp.ConnectAsync(sMTPEmailConfig.Host, sMTPEmailConfig.Port, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.AuthenticateAsync(sMTPEmailConfig.UserName, sMTPEmailConfig.AuthorizationCode);
await smtp.SendAsync(messageToSend);
@@ -60,16 +60,4 @@ namespace IRaCIS.Core.Application.Helper
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class DateTimeTranaslateAttribute : Attribute
{
public string Formart { get; set; }
public DateTimeTranaslateAttribute(string formart)
{
Formart = formart;
}
}
}
@@ -1,24 +1,19 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Wordprocessing;
using FellowOakDicom.Imaging.LUT;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore.Migrations;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using MiniExcelLibs;
using MiniExcelLibs.OpenXml;
using Newtonsoft.Json;
using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using SharpCompress.Common;
using System.Collections;
using System.Globalization;
using Xceed.Document.NET;
@@ -248,7 +243,6 @@ public static class ExcelExportHelper
}
public class DynamicColumnConfig
{
/// <summary>
@@ -266,9 +260,7 @@ public static class ExcelExportHelper
/// </summary>
public int TempalteLastColumnIndex { get; set; }
public bool IsCDISCExport { get; set; } = false;
//public List<string> CDISCList { get; set; } = new List<string>();
public List<string> CDISCList { get; set; } = new List<string>();
/// <summary>
/// 动态的列名 如果Id 重复,那么就按照名称填充,否则就按照Id 填充列数据
@@ -309,8 +301,6 @@ public static class ExcelExportHelper
public Guid Id { get; set; }
public string Name { get; set; }
public string CDISCCode { get; set; }
public override bool Equals(object obj)
{
if (obj is not ColumItem other) return false;
@@ -499,9 +489,7 @@ public static class ExcelExportHelper
if (dynamicColumnConfig != null)
{
//var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
var isCdics = dynamicColumnConfig.IsCDISCExport;
var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
var sheet = workbook.GetSheetAt(0);
@@ -556,9 +544,9 @@ public static class ExcelExportHelper
}
//创建新的列
for (int i = originRemoveEndIndex; i < originRemoveEndIndex + needAddCount; i++)
for (int i = originTotalEndIndex; i < originTotalEndIndex + needAddCount; i++)
{
titelRow.CreateCell(i + 1);
templateRow.CreateCell(i + 1);
@@ -577,11 +565,6 @@ public static class ExcelExportHelper
titelRow.GetCell(i).SetCellValue(titelRow.GetCell(i - gap).StringCellValue);
templateRow.GetCell(i).SetCellValue(templateRow.GetCell(i - gap).StringCellValue);
if (isCdics)
{
cdicsRow.GetCell(i).SetCellValue(cdicsRow.GetCell(i - gap).StringCellValue);
}
}
@@ -590,7 +573,6 @@ public static class ExcelExportHelper
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
{
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
titelRow.GetCell(i).SetCellValue(name);
@@ -598,15 +580,13 @@ public static class ExcelExportHelper
if (isCdics)
{
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
}
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.Write(memoryStream2, true);
@@ -629,11 +609,8 @@ public static class ExcelExportHelper
IgnoreTemplateParameterMissing = true,
};
//await MiniExcel.SaveAsByTemplateAsync("testmini.xlsx", templateStream.ToArray(), translateData);
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
memoryStream.Seek(0, SeekOrigin.Begin);
if (dynamicColumnConfig != null)
@@ -666,7 +643,7 @@ public static class ExcelExportHelper
var iteObjDic = itemObj.ToDictionary();
var itemDicName = iteObjDic.ContainsKey(dynamicColumnConfig.DynamicItemDicName) ? iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString() : "";
var itemDicName = iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString();
var itemValue = iteObjDic[dynamicColumnConfig.DynamicItemValueName]?.ToString();
//var writeIndex = itemList.IndexOf(itemObj) + dynamicColumnConfig.AutoColumnStartIndex;
@@ -686,61 +663,13 @@ public static class ExcelExportHelper
if (itemDicName.IsNotNullOrEmpty())
{
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
var translatedItemData = "";
//多选
if (optionTypeEnumStr == "1")
{
int[] enumValues = new int[0];
// 1. 反序列化 JSON 数组 (字符串枚举)
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
{
enumValues = new int[1] { int.Parse(itemValue) };
}
else
{
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue) ?? new int[0];
}
// 2. 翻译每一项并输出逗号拼接字符串
translatedItemData = string.Join(",",
enumValues.Select(code =>
dynamicTranslateDataList[itemDicName]
.FirstOrDefault(t =>
string.Equals(code.ToString(), t.Code, StringComparison.OrdinalIgnoreCase)
) is var r && r != null
? (isEn_US ? r.Value : r.ValueCN)
: string.Empty
));
}
else
{
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
row.GetCell(writeIndex).SetCellValue(translatedItemData);
}
else
{
var unit = iteObjDic.ContainsKey("Unit") ? iteObjDic["Unit"]?.ToString() : null;
if (unit.IsNotNullOrEmpty() && unit == "9")
{
row.GetCell(writeIndex).SetCellValue(itemValue + "%");
}
else
{
row.GetCell(writeIndex).SetCellValue(itemValue);
}
row.GetCell(writeIndex).SetCellValue(itemValue);
}
@@ -871,16 +800,17 @@ public static class ExcelExportHelper
//模板路径
var tplPath = physicalPath;
// 获取文件流
var templateStream = new MemoryStream();
#region sheet
// 打开模板文件
var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
var workbook = new XSSFWorkbook(templateFileStream);
// 获取文件流
var templateStream = new MemoryStream();
templateFile.CopyTo(templateStream);
templateStream.Seek(0, SeekOrigin.Begin);
var workbook = new XSSFWorkbook(templateStream);
int sheetCount = workbook.NumberOfSheets;
@@ -977,7 +907,7 @@ public static class ExcelExportHelper
{
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
titelRow.GetCell(i).SetCellValue(name);
@@ -985,8 +915,14 @@ public static class ExcelExportHelper
}
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
using (var memoryStream2 = new MemoryStream())
{
workbook.Write(memoryStream2, true);
memoryStream2.Seek(0, SeekOrigin.Begin);
templateStream = memoryStream2;
}
}
@@ -1084,91 +1020,6 @@ public static class ExcelExportHelper
public static async Task<IActionResult> MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment)
{
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
//模板路径
var tplPath = physicalPath;
var templateStream = new MemoryStream();
#region npoi
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
if (isEn_US)
{
// 打开模板文件
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
using var workbook = new XSSFWorkbook(templateFileStream);
int sheetCount = workbook.NumberOfSheets;
int removeRowIndex = 1; // 要删除的行(0-based
for (int i = 0; i < sheetCount; i++)
{
var sheet = workbook.GetSheetAt(i);
// 2 删除行
var row = sheet.GetRow(removeRowIndex);
if (row != null)
{
sheet.RemoveRow(row);
}
// 3 上移后续行
if (removeRowIndex < sheet.LastRowNum)
{
sheet.ShiftRows(
removeRowIndex + 1,
sheet.LastRowNum,
-1,
true, // copyRowHeight
false // resetOriginalRowHeight
);
}
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
}
else
{
using (var fs = new FileStream(tplPath, FileMode.Open, FileAccess.Read))
{
fs.CopyTo(templateStream);
}
templateStream.Position = 0;
}
#endregion
var memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), data, config);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
}
/// <summary>
/// 导出文件模板
/// </summary>
@@ -1250,7 +1101,4 @@ public static class ExcelExportHelper
};
}
}
@@ -69,15 +69,6 @@ public static class FileStoreHelper
return rootFolder;
}
public static string GetDonwnloadImageFolder(IWebHostEnvironment _hostEnvironment)
{
var rootPath = GetIRaCISRootPath(_hostEnvironment);
var rootFolder = Path.Combine(rootPath, StaticData.Folder.DownloadIamgeFolder);
return rootFolder;
}
//根据相对路径 获取具体文件物理地址
public static string GetPhysicalFilePath(IWebHostEnvironment _hostEnvironment, string relativePath)
{
@@ -2,7 +2,6 @@
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.Share;
using MassTransit.Mediator;
using System.Globalization;
namespace IRaCIS.Core.Application.Helper
{
@@ -58,29 +57,25 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron,string defaultCulture="")
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron)
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.QCTask:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId,CultureInfoName= defaultCulture }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId }, default), emailCron);
break;
case EmailBusinessScenario.CRCToQCQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
break;
case EmailBusinessScenario.QCToCRCImageQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
break;
//加急阅片 10分钟
case EmailBusinessScenario.ExpeditedReading:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
break;
default:
break;
@@ -88,25 +83,14 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron, string defaultCulture="")
public static void AddOrUpdateSystemCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
Console.WriteLine("更新项目到期job");
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
break;
default:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,106 +0,0 @@
using IdentityModel;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Helper.OtherTool;
public class WeComAlert
{
public string Env { get; set; } = "";
public string UserName { get; set; } = "";
public string Api { get; set; } = "";
public string Message { get; set; } = "";
public string? JsonData { get; set; }
public string? Stack { get; set; }
public bool HasStack => !string.IsNullOrWhiteSpace(Stack);
public string[] AtUsers { get; set; } = [];
}
public static class WeComNotifier
{
public static async Task SendAlertAsync(string webhook, WeComAlert alert)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
await Task.CompletedTask;
return;
}
try
{
var client = new RestClient();
var request = new RestRequest(webhook, Method.Post);
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 处理 @
var atText = alert.AtUsers != null && alert.AtUsers.Any()
? string.Join("", alert.AtUsers.Select(u => $" <@{u}>"))
: "";
// 处理堆栈
var stack = alert.Stack;
if (!string.IsNullOrWhiteSpace(stack))
{
stack = stack.Replace("\n", "\n> ");
if (stack.Length > 600)
stack = stack[..600] + "...(已截断)";
}
var markdown = $@"## 🚨 系统告警
> {atText}
> **** [{alert.Env}]({alert.Env})
> **** {time}
> **** {alert.UserName}
> **** {alert.Api}
###
<font color=""warning"">{alert.Message}</font>";
if (!string.IsNullOrWhiteSpace(alert.JsonData))
{
markdown += $@"
> **📦 JSON **
```json
{alert.JsonData}
```";
}
if (!string.IsNullOrWhiteSpace(stack))
{
markdown += $@"
###
<font color=""comment"">{stack}</font>";
}
var payload = new
{
msgtype = "markdown",
markdown = new { content = markdown }
};
request.AddHeader("Content-Type", "application/json");
request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json);
await client.ExecuteAsync(request);
}
catch (Exception ex)
{
Log.Logger.Error("企业微信告警发送失败: " + ex.Message);
}
}
}
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Helper
{
public static class SafeBussinessHelper
{
public static async Task<bool> RunAsync(Func<Task> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
{
try
{
await func();
return true;
}
catch (Exception ex)
{
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
return false;
}
}
public static async Task<(bool Success, T? Result)> RunAsync<T>(Func<Task<T>> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
{
try
{
var result = await func();
return (true, result);
}
catch (Exception ex)
{
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
return (false, default);
}
}
}
}
@@ -33,37 +33,38 @@
<ItemGroup>
<PackageReference Include="IdentityModel.OidcClient" Version="6.0.0" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DocX" Version="5.0.0" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="DocX" Version="4.0.25105.5786" />
<PackageReference Include="FreeSpire.Doc" Version="12.2.0" />
<PackageReference Include="ExcelDataReader" Version="3.8.0" />
<PackageReference Include="ExcelDataReader.DataSet" Version="3.8.0" />
<PackageReference Include="DistributedLock.Redis" Version="1.1.1" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.7" />
<PackageReference Include="fo-dicom" Version="5.2.6" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.6" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.7" />
<PackageReference Include="IP2Region.Net" Version="3.0.2" />
<PackageReference Include="MailKit" Version="4.15.1" />
<PackageReference Include="Masa.Contrib.Service.MinimalAPIs" Version="1.1.0" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.4.1" />
<PackageReference Include="Hangfire.Core" Version="1.8.18" />
<PackageReference Include="ExcelDataReader" Version="3.7.0" />
<PackageReference Include="ExcelDataReader.DataSet" Version="3.7.0" />
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="IP2Region.Net" Version="2.0.2" />
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="Masa.Contrib.Service.MinimalAPIs" Version="1.0.0" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="MimeKit" Version="4.15.1" />
<PackageReference Include="MiniExcel" Version="1.41.2" />
<PackageReference Include="Minio" Version="6.0.3" />
<PackageReference Include="MimeKit" Version="4.11.0" />
<PackageReference Include="MiniExcel" Version="1.40.0" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="MiniWord" Version="0.9.2" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="NPOI" Version="2.7.4" />
<PackageReference Include="NPOI" Version="2.7.3" />
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
<PackageReference Include="ZiggyCreatures.FusionCache" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
@@ -74,8 +75,4 @@
<Folder Include="Service\MinimalApiService\CodeTemplate\FrontTemplate\" />
</ItemGroup>
<ItemGroup>
<None Include="Service\TrialSiteUser\TrialStatService.cs" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -1,21 +1,8 @@
using IRaCIS.Core.Application.Helper;
using MiniExcelLibs.Attributes;
using MiniExcelLibs.Attributes;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.MassTransit.Command
{
/// <summary>
/// 全量一致性核查
/// </summary>
public record ConsistenFullCheckCommand
{
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
public Guid TrialId { get; set; }
public Guid InspectionFileId { get; set; }
}
public record ConsistenCheckCommand
{
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
@@ -30,26 +17,13 @@ namespace IRaCIS.Core.Application.MassTransit.Command
public class CheckDBModel : CheckViewModel
{
public Guid SubjectVisitId { get; set; }
public Guid StudyId { get; set; }
public string SiteCounty { get; set; }
}
public class FullCheckResult: CheckViewModel
{
public string LatestScanDateStr { get; set; } = string.Empty;
public string Modalitys { get; set; } = string.Empty;
public DateTime CheckTime { get; set; }
public string CheckResult { get; set; }
[DictionaryTranslateAttribute("CheckState")]
public CheckStateEnum CheckState { get; set; }
}
//[ExcelImporter(/*ImportResultFilter = typeof(ImportResultFilteTest),*/ IsLabelingError = true)]
@@ -88,13 +62,8 @@ namespace IRaCIS.Core.Application.MassTransit.Command
[ExcelColumnName("Modality")]
public string Modality { get; set; } = string.Empty;
#region
[DictionaryTranslateAttribute("Subject_Visit_Status")]
public SubjectStatus SubjectStatus { get; set; }
#endregion
public override bool Equals(object? obj)
{
@@ -21,14 +21,12 @@ public static class CommonEmailHelper
/// <param name="configInfo"></param>
/// <param name="messageToSend"></param>
/// <param name="emailFunc"></param>
/// <param name="userWorkLanguage"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public static async Task<EmailNoticeConfig> GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailNoticeConfig configInfo, MimeMessage messageToSend,
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc, UserWorkLanguage? userWorkLanguage = null)
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
{
bool isEn_US = userWorkLanguage.HasValue ? userWorkLanguage.Value == UserWorkLanguage.US : CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var (topicStr, htmlBodyStr) = isEn_US ? (configInfo.EmailTopic, configInfo.EmailHtmlContent) : (configInfo.EmailTopicCN, configInfo.EmailHtmlContentCN);
@@ -62,14 +60,13 @@ Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr
/// <param name="configInfo"></param>
/// <param name="messageToSend"></param>
/// <param name="emailFunc"></param>
/// <param name="userWorkLanguage"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public static async Task<TrialEmailNoticeConfig> GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(TrialEmailNoticeConfig configInfo, MimeMessage messageToSend,
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc, UserWorkLanguage? userWorkLanguage = null)
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
bool isEn_US = userWorkLanguage.HasValue ? userWorkLanguage.Value == UserWorkLanguage.US : CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var (topicStr, htmlBodyStr) = isEn_US ? (configInfo.EmailTopic, configInfo.EmailHtmlContent) : (configInfo.EmailTopicCN, configInfo.EmailHtmlContentCN);
@@ -97,20 +94,12 @@ Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr
return configInfo;
}
public static string ReplacePlatformName(SystemEmailSendConfig _systemEmailConfig, string needDealtxt, UserWorkLanguage? userWorkLanguage = null)
public static string ReplaceCompanyName(SystemEmailSendConfig _systemEmailConfig, string needDealtxt)
{
var isEn_US = userWorkLanguage.HasValue ? userWorkLanguage.Value == UserWorkLanguage.US : CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var platformName = isEn_US ? _systemEmailConfig.PlatformName : _systemEmailConfig.PlatformNameCN;
var str = needDealtxt.Replace("{platformName}", platformName);
return str;
}
public static string ReplaceCompanyName(SystemEmailSendConfig _systemEmailConfig, string needDealtxt, UserWorkLanguage? userWorkLanguage = null)
{
var isEn_US = userWorkLanguage.HasValue ? userWorkLanguage.Value == UserWorkLanguage.US : CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var str = needDealtxt.Replace("{company}", isEn_US ? _systemEmailConfig.CompanyName : _systemEmailConfig.CompanyNameCN)
.Replace("{company abbreviation}", isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN);
@@ -1,52 +1,53 @@
using AutoMapper;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Contracts.DTO;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using MaxMind.GeoIP2.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Globalization;
using System.IO;
using System.Text;
namespace IRaCIS.Core.Application.MassTransit.Consumer
{
public class ConsistencyCheckConsumer(
IRepository<DicomStudy> _studyRepository,
IUserInfo _userInfo,
IRepository<Subject> _subjectRepository,
IRepository<NoneDicomStudy> _noneDicomStudyRepository,
IRepository<SubjectVisit> _subjectVisitRepository,
IRepository<Dictionary> _dictionaryRepository,
IOSSService _oSSService,
IMapper _mapper,
IDictionaryService _dictionaryService,
IRepository<CommonDocument> _commonDocumentRepository,
IStringLocalizer _localizer,
IWebHostEnvironment _hostEnvironment,
IRepository<InspectionFile> _inspectionFileRepository,
IOptionsMonitor<SystemEmailSendConfig> _systemEmailSendConfig
) : IConsumer<ConsistenCheckCommand>, IConsumer<ConsistenFullCheckCommand>
public class ConsistencyCheckConsumer : IConsumer<ConsistenCheckCommand>
{
private readonly IRepository<DicomStudy> _studyRepository;
private readonly IUserInfo _userInfo;
private readonly IRepository<Subject> _subjectRepository;
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
private readonly IRepository<TrialSite> _trialSiteRepository;
private readonly IMapper _mapper;
private readonly IRepository<NoneDicomStudy> _noneDicomStudyRepository;
public IStringLocalizer _localizer { get; set; }
private readonly SystemEmailSendConfig _systemEmailConfig;
private readonly SystemEmailSendConfig _systemEmailConfig = _systemEmailSendConfig.CurrentValue;
private readonly IRepository<Dictionary> _dictionaryRepository;
/// <summary>
/// 构造函数注入
/// </summary>
public ConsistencyCheckConsumer(IRepository<DicomStudy> studyRepository, IUserInfo userInfo,
IRepository<Subject> subjectRepository, IRepository<SubjectVisit> subjectVisitRepository,
IRepository<TrialSite> trialSiteRepository, IRepository<NoneDicomStudy> noneDicomStudyRepository,
IMapper mapper, IStringLocalizer localizer, IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig, IRepository<Dictionary> dictionaryRepository)
{
_noneDicomStudyRepository = noneDicomStudyRepository;
_studyRepository = studyRepository;
_userInfo = userInfo;
_subjectRepository = subjectRepository;
_subjectVisitRepository = subjectVisitRepository;
_trialSiteRepository = trialSiteRepository;
_mapper = mapper;
_localizer = localizer;
_systemEmailConfig = systemEmailConfig.CurrentValue;
_dictionaryRepository = dictionaryRepository;
}
@@ -66,8 +67,8 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
//subjectVisitLambda2= subjectVisitLambda2.And(x => x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId && x.Subject.IsSubjectQuit == false && x.AuditState == AuditStateEnum.QCPassed &&
(x.CheckState == CheckStateEnum.ToCheck || x.CheckState == CheckStateEnum.CVIng);
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId &&
(x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
@@ -81,13 +82,11 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
Modality = study.ModalityForEdit,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
SiteCounty = sv.TrialSite.Country,
};
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectVisitId = sv.Id,
@@ -97,8 +96,6 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
Modality = noneDicomStudy.Modality,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
SiteCounty = sv.TrialSite.Country,
};
var dbList = (await dicomQuery.ToListAsync()).Union(await noneDicomQuey.ToListAsync()).ToList();
@@ -120,27 +117,12 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
foreach (var sv in svExcelGroup)
{
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
//Excel 的数据 在IRC 中可以找到该访视
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
{
var dbVisitStudyList = dbList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
//设置当前中心语言
var isEn_us = dbVisitStudyList.First().SiteCounty == StaticData.SiteCountry.US;
var cultureInfoName = StaticData.CultureInfo.en_US;
if (isEn_us == false)
{
cultureInfoName = StaticData.CultureInfo.zh_CN;
}
CultureInfo.CurrentCulture = new CultureInfo(cultureInfoName);
CultureInfo.CurrentUICulture = new CultureInfo(cultureInfoName);
//找到etc 当前visit site 和subject 一致的检查列表
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
@@ -219,7 +201,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
// excel 存在
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
var excelExceptDB = etcVisitStudyList.Except(dbCheckList);
//ETC 和系统的完全一致 两者没有差别
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
@@ -319,169 +301,12 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
}
await _subjectVisitRepository.SaveChangesAsync();
//await context.RespondAsync<ConsistenCheckResult>(new
//{
//});
}
public async Task Consume(ConsumeContext<ConsistenFullCheckCommand> context)
{
var inspectionFileId = context.Message.InspectionFileId;
var trialId = context.Message.TrialId;
//系统定义MRI 到底是MR 还是MRI
var mriModality = await _dictionaryRepository.Where(t => t.Parent.Code == "Modality").Where(t => t.Code == "MRI").Select(t => t.Value).FirstNotNullAsync();
//处理Excel大小写
context.Message.ETCList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.StudyDate = Convert.ToDateTime(t.StudyDate).ToString("yyyy-MM-dd"); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
var etcList = context.Message.ETCList;
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId /*&& x.Subject.IsSubjectQuit == false*/ && x.AuditState == AuditStateEnum.QCPassed;
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join study in _studyRepository.AsQueryable() on sv.Id equals study.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
SubjectVisitId = sv.Id,
SiteCode = sv.TrialSite.TrialSiteCode,
StudyDate = study.StudyTime == null ? string.Empty : ((DateTime)study.StudyTime).ToString("yyyy-MM-dd"),
StudyId = study.Id,
Modality = study.ModalityForEdit,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
};
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
SubjectVisitId = sv.Id,
SiteCode = sv.TrialSite.TrialSiteCode,
StudyDate = noneDicomStudy.ImageDate.ToString("yyyy-MM-dd"),
StudyId = noneDicomStudy.Id,
Modality = noneDicomStudy.Modality,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
};
var dbList = (await dicomQuery.ToListAsync()).Union(await noneDicomQuey.ToListAsync()).ToList();
//处理数据库 大小写
dbList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
var dbCheckList = _mapper.Map<List<CheckViewModel>>(dbList);
//按照Excel数据访视分组 按照数据库的数据 一个个的访视对比
var svExcelGroup = etcList.GroupBy(t => new { t.SiteCode, t.SubjectCode, t.VisitName })
.Select(g => new { g.Key.SubjectCode, g.Key.VisitName, g.Key.SiteCode, ExcelStudyList = g.ToList() }).ToList();
var fullCheckResultList = new List<FullCheckResult>();
foreach (var sv in svExcelGroup)
{
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
{
var dbVisitStudyList = dbList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
//找到etc 当前visit site 和subject 一致的检查列表
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
//以我们系统数据库为准 ,判断是MR 还是MRI, 把Excel 里的MR 或者MRI 处理成一致,MR MRI 是一致的
foreach (var item in etcVisitStudyList)
{
if (item.Modality == "MR" || item.Modality == "MRI")
{
item.Modality = mriModality;
}
}
//etc 和数据库 并集
var unionList = dbVisitStudyList.Union(etcVisitStudyList);
// 数据库存在
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
// excel 存在
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
var dbCurrentVisitFirst = dbVisitStudyList.First();
//ETC 和系统的完全一致 两者没有差别
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
{
//每个受试者每个访视加一条记录
fullCheckResultList.Add(new FullCheckResult()
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckTime = DateTime.Now,
CheckState = CheckStateEnum.CVPassed,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
VisitName = dbCurrentVisitFirst.VisitName,
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? ""
});
}
else
{
var checkResult =
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality/*, _systemEmailConfig.SystemShortName*/]}")) + " | "
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality, _systemEmailConfig.SystemShortName]}"));
//每个受试者每个访视加一条记录
fullCheckResultList.Add(new FullCheckResult()
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckState = CheckStateEnum.None,
CheckTime = DateTime.Now,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
VisitName = dbCurrentVisitFirst.VisitName,
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? "",
CheckResult = checkResult
});
}
}
}
//导到Excel 上传oss 回更记录状态
var list = fullCheckResultList;
var exportInfo = new ExcelExportInfo();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
var fileStreamResult = (FileStreamResult)await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialConsistentFUllCheckList_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(FullCheckResult));
var ossRelativePath = await _oSSService.UploadToOSSAsync(fileStreamResult.FileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", "DataReconciliation", uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DataReconciliation });
//var add = await _inspectionFileRepository.FindAsync(inspectionFileId);
//add.CheckState = EDCCheckState.Success;
//add.ResultPath = ossRelativePath;
//更新
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Success, ResultPath = ossRelativePath });
await _subjectVisitRepository.SaveChangesAsync();
}
}
}
@@ -27,16 +27,5 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer.Dto
}
public class SendEmailUserDto
{
public UserTypeEnum UserTypeEnum { get; set; }
public string FullName { get; set; }
public UserWorkLanguage UserWorkLanguage { get; set; }
public string EMail { get; set; }
}
}
@@ -256,7 +256,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送影像重传相关邮件的通用方法 已经处理项目
/// 发送影像重传相关邮件的通用方法
/// </summary>
private async Task SendImageReuploadEmail(SendImageReuploadEmailInDto inDto)
{
@@ -266,58 +266,57 @@ public class ImageConsumer(
// 获取项目信息
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == inDto.TrialId);
List<UserTypeEnum> filterUserTypeList = new List<UserTypeEnum>()
{
UserTypeEnum.ClinicalResearchCoordinator,
UserTypeEnum.CRA,
};
// 根据不同场景获取不同角色的用户 先排除CRC和CRA
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == inDto.TrialId && !x.TrialUser.IsDeleted && !filterUserTypeList.Contains(x.UserRole.UserTypeEnum) ).Include(x => x.UserRole).Select(x => x.UserRole)
.Select(x => new SendEmailUserDto()
{
UserTypeEnum = x.UserTypeEnum,
FullName = x.FullName,
UserWorkLanguage = x.IdentityUser.UserWorkLanguage,
EMail=x.EMail,
})
.ToListAsync();
// CRC和CRA单独取
var crcAndcraUserList = await _trialSiteRepository.Where(x => x.Id == inDto.SubjectVisit.TrialSiteId).SelectMany(x => x.CRCUserList.Select(y => y.UserRole))
.Select(x => new SendEmailUserDto()
{
UserTypeEnum = x.UserTypeEnum,
FullName = x.FullName,
UserWorkLanguage = x.IdentityUser.UserWorkLanguage,
EMail = x.EMail,
}).ToListAsync();
trialUserList.AddRange(crcAndcraUserList);
// 根据不同场景获取不同角色的用户
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == inDto.TrialId && !x.TrialUser.IsDeleted).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
// 根据场景确定收件人
List<SendEmailUserDto> toUserList = new List<SendEmailUserDto>();
List<SendEmailUserDto> ccUserList = new List<SendEmailUserDto>();
List<UserRole> toUserList = new List<UserRole>();
List<UserRole> ccUserList = new List<UserRole>();
var emailNoticeUserList = await _emailNoticeUserTypeRepository.Where(x => x.EmailNoticeConfigId == inDto.EmailNoticeConfig.Id).ToListAsync();
var userTypeEnumList = emailNoticeUserList.Select(x => x.UserType).ToList();
var userTypeEnum = emailNoticeUserList.Select(x => x.UserType).ToList();
if (inDto.UserTypes != null)
{
userTypeEnumList = inDto.UserTypes;
userTypeEnum = inDto.UserTypes;
}
var crcUserList = new List<UserRole>() { };
if (userTypeEnum.Contains(UserTypeEnum.ClinicalResearchCoordinator))
{
crcUserList = await _trialSiteRepository.Where(x => x.Id == inDto.SubjectVisit.TrialSiteId).SelectMany(x => x.CRCUserList.Select(y => y.UserRole)).ToListAsync();
}
if (inDto.UserTypes == null)
{
var toList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.To).Select(x => x.UserType).ToList();
toUserList = trialUserList.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
var toList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.To && x.UserType != UserTypeEnum.ClinicalResearchCoordinator).Select(x => x.UserType).ToList();
toUserList = trialUser.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
if (emailNoticeUserList.Any(x => x.EmailUserType == EmailUserType.To && x.UserType == UserTypeEnum.ClinicalResearchCoordinator))
{
toUserList.AddRange(crcUserList);
}
}
else
{
var toList = inDto.UserTypes;
toUserList = trialUserList.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
var toList = inDto.UserTypes.Where(x => x != UserTypeEnum.ClinicalResearchCoordinator).ToList();
toUserList = trialUser.Where(x => toList.Contains(x.UserTypeEnum)).ToList();
if (inDto.UserTypes.Any(x => x == UserTypeEnum.ClinicalResearchCoordinator))
{
toUserList.AddRange(crcUserList);
}
}
var ccList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.Copy).Select(x => x.UserType).ToList();
ccUserList = trialUserList.Where(x => ccList.Contains(x.UserTypeEnum)).ToList();
var ccList = emailNoticeUserList.Where(x => x.EmailUserType == EmailUserType.Copy && x.UserType != UserTypeEnum.ClinicalResearchCoordinator).Select(x => x.UserType).ToList();
ccUserList = trialUser.Where(x => ccList.Contains(x.UserTypeEnum)).ToList();
if (emailNoticeUserList.Any(x => x.EmailUserType == EmailUserType.Copy && x.UserType == UserTypeEnum.ClinicalResearchCoordinator))
{
ccUserList.AddRange(crcUserList);
}
// 如果没有收件人,则不发送邮件
if (toUserList.Count == 0)
@@ -349,55 +348,40 @@ public class ImageConsumer(
DictionaryList = dictionaryDtos
});
var userWorkLanguageList = toUserList.Select(x => x.UserWorkLanguage).Distinct().ToList();
foreach (var workLanguage in userWorkLanguageList)
foreach (var userinfo in toUserList)
{
var userinfoList = toUserList.Where(x => x.UserWorkLanguage == workLanguage).ToList();
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
}
var userNames = userinfoList.Select(x => x.FullName).ToList();
// 格式化邮件内容
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = inDto.SubjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, inDto.SubjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
string.Join(',', userNames), // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
inDto.SubjectVisit.VisitName, // 访视 {3}
dictionValue[0], // 是否加急 {4}
dictionValue[1], // 审批结果 {5}
_systemEmailConfig.SiteUrl // 链接 {6}
);
{
var subjectCode = inDto.SubjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, inDto.SubjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
inDto.SubjectVisit.VisitName, // 访视 {3}
dictionValue[0], // 是否加急 {4}
dictionValue[1], // 审批结果 {5}
_systemEmailConfig.SiteUrl // 链接 {6}
);
return (topicStr, htmlBodyStr);
};
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -464,7 +448,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送阅片人筛选相关邮件的通用方法 已经处理项目
/// 发送阅片人筛选相关邮件的通用方法
/// </summary>
private async Task SendReviewerSelectionEmail(EmailNoticeConfig emailNoticeConfig, Guid trialId, List<Guid> enrollIdList)
{
@@ -474,19 +458,11 @@ public class ImageConsumer(
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
// 根据不同场景获取不同角色的用户
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == trialId && !x.TrialUser.IsDeleted).Include(x => x.UserRole).Select(x => x.UserRole)
.Select(x => new SendEmailUserDto()
{
UserTypeEnum = x.UserTypeEnum,
FullName = x.FullName,
UserWorkLanguage = x.IdentityUser.UserWorkLanguage,
EMail = x.EMail,
})
.ToListAsync();
var trialUser = await _trialUseRoleRepository.Where(x => x.TrialId == trialId && !x.TrialUser.IsDeleted).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
// 根据场景确定收件人
List<SendEmailUserDto> toUserList = new List<SendEmailUserDto>();
List<SendEmailUserDto> ccUserList = new List<SendEmailUserDto>();
List<UserRole> toUserList = new List<UserRole>();
List<UserRole> ccUserList = new List<UserRole>();
var emailNoticeUserList = await _emailNoticeUserTypeRepository.Where(x => x.EmailNoticeConfigId == emailNoticeConfig.Id).ToListAsync();
@@ -505,35 +481,26 @@ public class ImageConsumer(
return;
}
var userWorkLanguageList = toUserList.Select(x => x.UserWorkLanguage).Distinct().ToList();
foreach (var workLanguage in userWorkLanguageList)
foreach (var userinfo in toUserList)
{
var userinfoList = toUserList.Where(x => x.UserWorkLanguage == workLanguage).ToList();
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
}
var userNames = userinfoList.Select(x => x.FullName).ToList();
// 格式化邮件内容
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
string.Join(',', userNames), // 用户名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
"", // 受试者 {2} - 阅片人筛选不涉及受试者
"", // 访视 {3} - 阅片人筛选不涉及访视
@@ -545,9 +512,9 @@ public class ImageConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
@@ -1,6 +1,5 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.MassTransit.Consumer.Dto;
using IRaCIS.Core.Domain;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Domain.Models;
@@ -69,7 +68,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).Include(x => x.IdentityUser).FirstOrDefaultAsync();
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).FirstOrDefaultAsync();
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
@@ -79,23 +78,22 @@ public class UrgentMedicalReviewAddedEventConsumer(
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == visitid).FirstOrDefaultAsync();
var workLanguage = userinfo.IdentityUser?.UserWorkLanguage;
var userIsEn_US = workLanguage.HasValue ? workLanguage.Value == UserWorkLanguage.US : isEn_US;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = userIsEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = userIsEn_US,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
@@ -112,7 +110,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
var subjectName = taskInfo.BlindSubjectCode.IsNullOrEmpty() ? taskInfo.Subject.Code : taskInfo.BlindSubjectCode;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectName, taskInfo.TaskBlindName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
criterion.CriterionName, // 阅片标准 {2}
@@ -128,9 +126,9 @@ public class UrgentMedicalReviewAddedEventConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -179,7 +177,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).Include(x => x.IdentityUser).FirstOrDefaultAsync();
var userinfo = await _userRoleRepository.Where(x => x.Id == medicalReview.MedicalManagerUserId).FirstOrDefaultAsync();
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
@@ -189,24 +187,22 @@ public class UrgentIRRepliedMedicalReviewConsumer(
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == visitid).FirstOrDefaultAsync();
var workLanguage = userinfo.IdentityUser?.UserWorkLanguage;
var userIsEn_US = workLanguage.HasValue ? workLanguage.Value == UserWorkLanguage.US : isEn_US;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = userIsEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = userIsEn_US,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
@@ -223,7 +219,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
var subjectCode = taskInfo.BlindSubjectCode.IsNullOrEmpty() ? taskInfo.Subject.Code : taskInfo.BlindSubjectCode;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, taskInfo.TaskBlindName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
criterion.CriterionName, // 阅片标准 {2}
@@ -238,9 +234,9 @@ public class UrgentIRRepliedMedicalReviewConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -297,7 +293,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
var criterion = await _readingQuestionCriterionTrialRepository.FirstOrDefaultAsync(x => x.Id == medicalReview.VisitTask.TrialReadingCriterionId);
var taskInfo = await _visitTaskRepository.Where(x => x.Id == medicalReview.VisitTaskId).Include(x => x.SourceSubjectVisit).Include(x => x.ReadModule).Include(x => x.Subject).FirstNotNullAsync();
var userinfo = await _userRoleRepository.Where(x => x.Id == taskInfo.DoctorUserId).Include(x => x.IdentityUser).FirstOrDefaultAsync();
var userinfo = await _userRoleRepository.Where(x => x.Id == taskInfo.DoctorUserId).FirstOrDefaultAsync();
@@ -307,25 +303,22 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == visitid).FirstOrDefaultAsync();
var workLanguage = userinfo.IdentityUser?.UserWorkLanguage;
var userIsEn_US = workLanguage.HasValue ? workLanguage.Value == UserWorkLanguage.US : isEn_US;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = userIsEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = userIsEn_US,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
@@ -342,7 +335,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
var subjectCode = taskInfo.BlindSubjectCode.IsNullOrEmpty() ? taskInfo.Subject.Code : taskInfo.BlindSubjectCode;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, taskInfo.TaskBlindName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
@@ -359,8 +352,9 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
@@ -406,15 +400,7 @@ public class UrgentIRApplyedReReadingConsumer(
var doctorInfo = await _userRoleRepository.Where(x => x.Id == taskInfo.DoctorUserId).FirstOrDefaultAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == taskInfo.TrialId && x.TrialUser.IsDeleted==false).Include(x => x.UserRole).Select(x => x.UserRole)
.Select(x => new SendEmailUserDto()
{
UserTypeEnum = x.UserTypeEnum,
FullName = x.FullName,
UserWorkLanguage = x.IdentityUser.UserWorkLanguage,
EMail = x.EMail,
})
.ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == taskInfo.TrialId && x.TrialUser.IsDeleted==false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ProjectManager || x.UserTypeEnum == UserTypeEnum.APM).ToList();
if (context.Message.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed)
@@ -427,48 +413,37 @@ public class UrgentIRApplyedReReadingConsumer(
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == visitid).FirstOrDefaultAsync();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == taskInfo.TrialId);
var userWorkLanguageList = userinfoList.Select(x => x.UserWorkLanguage).Distinct().ToList();
foreach (var workLanguage in userWorkLanguageList)
foreach (var userinfo in userinfoList)
{
var langUserinfoList = userinfoList.Where(x => x.UserWorkLanguage == workLanguage).ToList();
if (!langUserinfoList.Any()) continue;
var userIsEn_US = workLanguage == UserWorkLanguage.US;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = userIsEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "ReadingCategory",EnumValue=taskInfo.ReadingCategory.GetEnumInt(), }, //任务类型
new DictionaryDto (){DictionaryCode= "RequestReReadingResult",EnumValue="0", }, //审批结果 都是待审批
}
});
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
foreach (var userinfo in langUserinfoList)
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == taskInfo.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
var companyName = userIsEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var userNames = langUserinfoList.Select(x => x.FullName).ToList();
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "ReadingCategory",EnumValue=taskInfo.ReadingCategory.GetEnumInt(), }, //任务类型
new DictionaryDto (){DictionaryCode= "RequestReReadingResult",EnumValue="0", }, //审批结果 都是待审批
}
});
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = taskInfo.BlindSubjectCode.IsNullOrEmpty() ? taskInfo.Subject.Code : taskInfo.BlindSubjectCode;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, taskInfo.TaskBlindName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
string.Join(',', userNames), // 用户名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
taskInfo.TaskBlindName, // 访视 {3}
@@ -477,14 +452,18 @@ public class UrgentIRApplyedReReadingConsumer(
criterion.CriterionName, // 阅片标准 {6}
dictionValue[1], // 审批结果 {7}
_systemEmailConfig.SiteUrl // 链接 {8}
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -2,7 +2,6 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Vml;
using DocumentFormat.OpenXml.Wordprocessing;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
@@ -56,86 +55,74 @@ public class UserSiteSurveySubmitedEventConsumer(
if (emailConfig != null)
{
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
.Select(t => new { t.UserRole.FullName, t.UserRole.IdentityUser.EMail, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
.Select(t => new { t.UserRole.FullName, t.UserRole.IdentityUser.EMail, t.UserRole.UserTypeEnum }).ToListAsync();
var sPMOrCPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.SPM || t.UserTypeEnum == UserTypeEnum.CPM).ToList();
var pmAndAPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserTypeEnum == UserTypeEnum.APM).ToList();
var messageToSend = new MimeMessage();
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var toUserName = string.Empty;
//有SPM
if (sPMOrCPMList.Count > 0)
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var messageToSend = new MimeMessage();
var toUserName = string.Empty;
//有SPM 并且参与
if (trialInfo.IsSPMJoinSiteSurvey && sPMOrCPMList.Count > 0)
foreach (var user in sPMOrCPMList)
{
var lan_SPMOrCPMList = sPMOrCPMList.Where(t => t.UserWorkLanguage == workLanguage);
foreach (var user in lan_SPMOrCPMList)
{
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
toUserName = string.Join('、', lan_SPMOrCPMList.Select(t => t.FullName));
foreach (var user in pmAndAPMList)
{
messageToSend.Cc.Add(new MailboxAddress(user.FullName, user.EMail));
}
}
else
{
var lan_pmAndAPMList = pmAndAPMList.Where(t => t.UserWorkLanguage == workLanguage);
foreach (var user in lan_pmAndAPMList)
{
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
toUserName = string.Join('、', lan_pmAndAPMList.Select(t => t.FullName));
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
toUserName = string.Join('、', sPMOrCPMList.Select(t => t.FullName));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
foreach (var user in pmAndAPMList)
{
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName,
siteInfo.TrialSiteCode,
siteInfo.TrialSiteAliasName,
messageToSend.Cc.Add(new MailboxAddress(user.FullName, user.EMail));
}
siteSurveyInfo.UserName,
siteSurveyInfo.Email,
siteSurveyInfo.Phone,
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
}
else
{
foreach (var user in pmAndAPMList)
{
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
toUserName = string.Join('、', pmAndAPMList.Select(t => t.FullName));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
toUserName,
siteInfo.TrialSiteCode,
siteInfo.TrialSiteAliasName,
siteSurveyInfo.UserName,
siteSurveyInfo.Email,
siteSurveyInfo.Phone,
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -164,74 +151,61 @@ public class SiteSurveySPMSubmitedEventConsumer(
var trialId = siteSurveyInfo.TrialId;
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var trialUserList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToList();
var scenario = EmailBusinessScenario.Approval_SubmitSiteSurvey;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
var messageToSend = new MimeMessage();
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var trialUserList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum }).ToList();
var sPMOrCPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.SPM || t.UserTypeEnum == UserTypeEnum.CPM).ToList();
var pmAndAPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserTypeEnum == UserTypeEnum.APM).ToList();
var toUserName = string.Empty;
foreach (var item in pmAndAPMList)
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var sPMOrCPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.SPM || t.UserTypeEnum == UserTypeEnum.CPM).ToList();
var pmAndAPMList = trialUserList.Where(t => t.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserTypeEnum == UserTypeEnum.APM).ToList();
var messageToSend = new MimeMessage();
var toUserName = string.Empty;
var lan_pmAndAPMList = pmAndAPMList.Where(t => t.UserWorkLanguage == workLanguage);
foreach (var item in lan_pmAndAPMList)
{
messageToSend.To.Add(new MailboxAddress(item.FullName, item.EMail));
toUserName = string.Join('、', lan_pmAndAPMList.Select(t => t.FullName));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName,
siteInfo.TrialSiteCode, //中心编号
siteInfo.TrialSiteAliasName,//中心名称
siteSurveyInfo.UserName, //联系人
siteSurveyInfo.Email, //联系邮箱
siteSurveyInfo.Phone, //联系电话
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
messageToSend.To.Add(new MailboxAddress(item.FullName, item.EMail));
toUserName = string.Join('、', pmAndAPMList.Select(t => t.FullName));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
toUserName,
siteInfo.TrialSiteCode, //中心编号
siteInfo.TrialSiteAliasName,//中心名称
siteSurveyInfo.UserName, //联系人
siteSurveyInfo.Email, //联系邮箱
siteSurveyInfo.Phone, //联系电话
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -260,108 +234,92 @@ public class SiteSurverRejectedEventConsumer(
var trialSiteSurveyId = context.Message.TrialSiteSurveyId;
var siteSurveyInfo = _trialSiteSurveyRepository.Where(t => t.Id == trialSiteSurveyId, ignoreQueryFilters: true).FirstOrDefault().IfNullThrowException();
var siteSurveyInfo = _trialSiteSurveyRepository.Where(t => t.Id == trialSiteSurveyId ,ignoreQueryFilters:true).FirstOrDefault().IfNullThrowException();
var trialId = siteSurveyInfo.TrialId;
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var scenario = EmailBusinessScenario.SiteSurveyReject;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var messageToSend = new MimeMessage();
var toUserName = siteSurveyInfo.UserName;
if (context.Message.IsHaveSPMOrCPM)
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var messageToSend = new MimeMessage();
var toUserName = siteSurveyInfo.UserName;
if (context.Message.IsHaveSPMOrCPM)
//PM 驳回到SPM
if (siteSurveyInfo.State == TrialSiteSurveyEnum.CRCSubmitted)
{
//PM 驳回到SPM
if (siteSurveyInfo.State == TrialSiteSurveyEnum.CRCSubmitted)
//var user = await _userRoleRepository.FirstOrDefaultAsync(t => t.Id == siteSurveyInfo.PreliminaryUserId);
//name = user.FullName;
var sPMOrCPMList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum }).ToList();
foreach (var user in sPMOrCPMList)
{
var sPMOrCPMList = _trialUserRoleRepository.Where(t => t.TrialId == trialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToList();
var lan_SPMOrCPMList = sPMOrCPMList.Where(t => t.UserWorkLanguage == workLanguage);
foreach (var user in lan_SPMOrCPMList)
{
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
toUserName = string.Join('、', lan_SPMOrCPMList.Select(t => t.FullName));
messageToSend.To.Add(new MailboxAddress(user.FullName, user.EMail));
}
//SPM 驳回到CRC
else if (siteSurveyInfo.State == TrialSiteSurveyEnum.ToSubmit)
{
//使用中心国家语言 一致才发送
if ((siteInfo.Country == StaticData.SiteCountry.US && isEn_US) || (siteInfo.Country == StaticData.SiteCountry.CN && isEn_US == false))
{
messageToSend.To.Add(new MailboxAddress(toUserName, siteSurveyInfo.Email));
}
}
toUserName = string.Join('、', sPMOrCPMList.Select(t => t.FullName));
}
else
//SPM 驳回到CRC
else if (siteSurveyInfo.State == TrialSiteSurveyEnum.ToSubmit)
{
//没有SPM PM驳回到CRC
//使用中心国家语言 一致才发送
if ((siteInfo.Country == StaticData.SiteCountry.US && isEn_US) || (siteInfo.Country == StaticData.SiteCountry.CN && isEn_US == false))
{
messageToSend.To.Add(new MailboxAddress(toUserName, siteSurveyInfo.Email));
}
messageToSend.To.Add(new MailboxAddress(toUserName, siteSurveyInfo.Email));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName,
trialInfo.TrialCode,
trialInfo.ResearchProgramNo,
trialInfo.ExperimentName,
siteInfo.TrialSiteCode, //中心编号
siteInfo.TrialSiteAliasName,//中心名称
siteSurveyInfo.LatestBackReason, //驳回原因
_systemEmailConfig.SiteUrl, //链接
(siteSurveyInfo.State == TrialSiteSurveyEnum.ToSubmit ? "inline - block" : "none")
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
}
else
{
//没有SPM PM驳回到CRC
messageToSend.To.Add(new MailboxAddress(String.Empty, siteSurveyInfo.Email));
}
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName,trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
toUserName,
trialInfo.TrialCode,
trialInfo.ResearchProgramNo,
trialInfo.ExperimentName,
siteInfo.TrialSiteCode, //中心编号
siteInfo.TrialSiteAliasName,//中心名称
siteSurveyInfo.LatestBackReason, //驳回原因
_systemEmailConfig.SiteUrl, //链接
(siteSurveyInfo.State == TrialSiteSurveyEnum.ToSubmit ? "inline - block" : "none")
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -36,7 +36,7 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<CRCSubmitedAndQCToAuditEvent> context)
{
Log.Logger.Warning("发送(005,006) 【加急项目所有IQC待领取质控任务】邮件!!!");
Console.WriteLine("发送(005,006) 【加急项目所有IQC待领取质控任务】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var subjectVisitId = context.Message.SubjectVisitId;
@@ -49,91 +49,80 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
if (trialEmailConfig != null)
{
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
var auditStateCode = "AuditStatePE";
if (trialInfo.QCProcessEnum == TrialQCProcess.DoubleAudit)
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
auditStateCode = "AuditStateRC";
}
var iqcList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).Where(t => t.UserWorkLanguage == workLanguage).ToList();
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
var auditStateCode = "AuditStatePE";
if (trialInfo.QCProcessEnum == TrialQCProcess.DoubleAudit)
{
auditStateCode = "AuditStateRC";
}
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= auditStateCode,EnumValue=subjectVisit.AuditState.GetEnumInt(), }, //审核状态
}
});
});
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
foreach (var userinfo in iqcList)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
var userNames = iqcList.Select(x => x.FullName).ToList();
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
string.Join(',', userNames), // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
subjectVisit.VisitName, // 访视 {3}
dictionValue[0], // 审核状态 {4}
_systemEmailConfig.SiteUrl // 链接 {5}
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
}
var userNames = userinfoList.Select(x => x.FullName).ToList();
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
string.Join(',', userNames), // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
subjectVisit.VisitName, // 访视 {3}
dictionValue[0], // 审核状态 {4}
_systemEmailConfig.SiteUrl // 链接 {5}
);
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -158,7 +147,7 @@ public class CRCRepliedQCChallengeEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<CRCRepliedQCChallengeEvent> context)
{
Log.Logger.Warning("发送(Code012013) 【CRC 回复质控质疑 通知QC】邮件!!!");
Console.WriteLine("发送(Code012013) 【CRC 回复质控质疑 通知QC】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var subjectVisitId = context.Message.SubjectVisitId;
@@ -169,37 +158,34 @@ public class CRCRepliedQCChallengeEventConsumer(
var scenario = context.Message.IsPd ? EmailBusinessScenario.PDVerification_UnderQCQuery : EmailBusinessScenario.EligibilityVerification_Pending;
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
if (trialEmailConfig != null)
{
var qCChallengeDialog = await _qCChallengeDialogRepository.Where(x => x.Id == context.Message.QCChallengeDialogId).Include(x => x.QCChallenge).FirstNotNullAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.IQC).ToList();
var pmandAPm = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
var userinfo = subjectVisit.CurrentActionUser;
//重新设置当前发送邮件的语言
var workLanguage = _userRoleRepository.Where(t => t.Id == userinfo.Id).Select(t => t.IdentityUser.UserWorkLanguage).First();
isEn_US = workLanguage == UserWorkLanguage.US;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -221,7 +207,7 @@ public class CRCRepliedQCChallengeEventConsumer(
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
@@ -233,9 +219,9 @@ public class CRCRepliedQCChallengeEventConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
@@ -258,7 +244,7 @@ public class QCRepliedQCChallengeEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<QCRepliedQCChallengeEvent> context)
{
Log.Logger.Warning("发送(014015) 【 QC回复 质控质疑,通知CRC】邮件!!!");
Console.WriteLine("发送(014015) 【 QC回复 质控质疑,通知CRC】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var subjectVisitId = context.Message.SubjectVisitId;
@@ -291,61 +277,54 @@ public class QCRepliedQCChallengeEventConsumer(
}
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
var craInfo = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).Where(t => t.UserWorkLanguage == workLanguage).ToList();
var craInfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).ToList();
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "YesOrNo",EnumValue=isclose.ToString().ToLower(), }, //是否关闭
}
});
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "YesOrNo",EnumValue=isclose.ToString().ToLower(), }, //是否关闭
}
});
foreach (var userinfo in userinfoList)
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var toUserName = string.Join('、', userinfoList.Select(t => t.FullName));
foreach (var userinfo in userinfoList)
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
if (craInfo != null)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
foreach (var user in craInfoList)
{
messageToSend.Cc.Add(new MailboxAddress(user.FullName, user.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName, // 用户名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
subjectVisit.VisitName, // 访视 {3}
@@ -358,13 +337,10 @@ public class QCRepliedQCChallengeEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
}
@@ -388,7 +364,7 @@ public class CRCRepliedCheckChallengeEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<CRCRepliedCheckChallengeEvent> context)
{
Log.Logger.Warning("发送(019,020) 【 CRC 回复一致性核查质疑 通知PM】邮件!!!");
Console.WriteLine("发送(019,020) 【 CRC 回复一致性核查质疑 通知PM】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
@@ -407,58 +383,49 @@ public class CRCRepliedCheckChallengeEventConsumer(
{
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //审核状态
}
});
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager)
.Where(t => t.UserWorkLanguage == workLanguage).ToList();
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //审核状态
}
});
foreach (var userinfo in userinfoList)
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var toUserName = string.Join('、', userinfoList.Select(t => t.FullName));
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName, // 用户名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
subjectVisit.VisitName, // 访视 {3}
@@ -470,17 +437,10 @@ public class CRCRepliedCheckChallengeEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
}
@@ -506,12 +466,12 @@ public class PMRepliedCheckChallengeEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<PMRepliedCheckChallengeEvent> context)
{
Log.Logger.Warning("发送(016017) 【 PM 一致性核查 通知CRC】邮件!!!");
Console.WriteLine("发送(016017) 【 PM 一致性核查 通知CRC】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var subjectVisitId = context.Message.SubjectVisitId;
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.NoneDicomStudyList).Include(x => x.StudyList).Include(x => x.Subject).Include(t => t.TrialSite).FirstNotNullAsync();
var subjectVisit = await _subjectVisitRepository.Where(x => x.Id == subjectVisitId).Include(x => x.NoneDicomStudyList).Include(x => x.StudyList).Include(x => x.Subject).FirstNotNullAsync();
var trialId = subjectVisit.TrialId;
@@ -526,11 +486,11 @@ public class PMRepliedCheckChallengeEventConsumer(
var checkChallengeDialog = await _checkChallengeDialogRepository.Where(x => x.Id == context.Message.CheckChallengeDialogId).FirstNotNullAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).ToList();
var craInfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).ToList();
var craInfo = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.CRA).FirstOrDefault();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
@@ -539,46 +499,28 @@ public class PMRepliedCheckChallengeEventConsumer(
modalities = subjectVisit.NoneDicomStudyList.Select(t => t.Modality)
.Union(subjectVisit.StudyList.Select(k => k.ModalityForEdit)).ToList();
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
//foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
//重新设置当前发送邮件的语言
isEn_US = subjectVisit.TrialSite.Country == StaticData.SiteCountry.US;
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //核查状态
var workLanguage = isEn_US ? UserWorkLanguage.US : UserWorkLanguage.CN;
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator).Where(t => t.UserWorkLanguage == workLanguage).ToList();
}
});
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //核查状态
}
});
foreach (var userinfo in userinfoList)
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var toUserName = string.Join('、', userinfoList.Select(t => t.FullName));
foreach (var userinfo in userinfoList)
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
if (craInfo != null)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
foreach (var user in craInfoList)
{
messageToSend.Cc.Add(new MailboxAddress(user.FullName, user.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -589,8 +531,8 @@ public class PMRepliedCheckChallengeEventConsumer(
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName, // 用户名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
@@ -605,11 +547,10 @@ public class PMRepliedCheckChallengeEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
}
@@ -651,57 +592,49 @@ public class CheckStateChangedToAuditEventConsumer(
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false)
.Select(t => new { t.UserRole.IdentityUser.EMail, t.UserRole.FullName, t.UserRole.UserTypeEnum, t.UserRole.IdentityUser.UserWorkLanguage }).ToListAsync();
var trialUserList = await _trialUseRoleRepository.Where(x => x.TrialId == subjectVisit.TrialId && x.TrialUser.IsDeleted == false).Include(x => x.UserRole).Select(x => x.UserRole).ToListAsync();
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager).ToList();
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == subjectVisit.TrialId);
// 遍历语言枚举 在底层判断收件人是否为空,决定是否跳过邮件发送,避免每个邮件场景的地方都重复判断
foreach (UserWorkLanguage workLanguage in Enum.GetValues<UserWorkLanguage>())
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
//重新设置当前发送邮件的语言
isEn_US = workLanguage == UserWorkLanguage.US;
var userinfoList = trialUserList.Where(x => x.UserTypeEnum == UserTypeEnum.APM || x.UserTypeEnum == UserTypeEnum.ProjectManager)
.Where(t => t.UserWorkLanguage == workLanguage).ToList();
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //审核状态
var dictionValue = await CommonEmailHelper.TranslationDictionary(new TranslationDictionaryDto()
{
DictionaryRepository = _dictionaryRepository,
IsEn_US = isEn_US,
DictionaryList = new List<DictionaryDto>()
{
new DictionaryDto (){DictionaryCode= "CheckState",EnumValue=subjectVisit.CheckState.GetEnumInt(), }, //审核状态
}
});
}
});
foreach (var userinfo in userinfoList)
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var toUserName = string.Join('、', userinfoList.Select(t => t.FullName));
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
toUserName, // 姓名 {0}
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 姓名 {0}
trialInfo.ExperimentName, // 项目 {1}
subjectCode, // 受试者 {2}
subjectVisit.VisitName, // 访视 {3}
@@ -713,11 +646,10 @@ public class CheckStateChangedToAuditEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
}
@@ -739,7 +671,7 @@ public class QCClaimTaskEventConsumer(
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<QCClaimTaskEvent> context)
{
Log.Logger.Warning("发送(Code007008) 【QC 领取了质控任务】邮件!!!");
Console.WriteLine("发送(Code007008) 【QC 领取了质控任务】邮件!!!");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
@@ -771,23 +703,15 @@ public class QCClaimTaskEventConsumer(
var userinfo = subjectVisit.CurrentActionUser;
if (userinfo == null)
{
return;
}
//重新设置当前发送邮件的语言
var workLanguage = _userRoleRepository.Where(t => t.Id == userinfo.Id).Select(t => t.IdentityUser.UserWorkLanguage).First();
isEn_US = workLanguage == UserWorkLanguage.US;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -817,7 +741,7 @@ public class QCClaimTaskEventConsumer(
var subjectCode = subjectVisit.Subject.Code;
var topicStr = string.Format(input.topicStr, trialInfo.ResearchProgramNo, subjectCode, subjectVisit.VisitName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr, workLanguage),
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.FullName, // 用户名 {0}
trialInfo.ExperimentName, // 项目 {1}
string.Join(',', subjectcodes), // 受试者 {2}
@@ -831,9 +755,9 @@ public class QCClaimTaskEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
@@ -1,16 +1,14 @@
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using Microsoft.Extensions.Logging;
using NPOI.SS.Formula.Functions;
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger, IConfiguration _config) : IFilter<ConsumeContext<T>> where T : DomainEvent
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger) : IFilter<ConsumeContext<T>> where T : DomainEvent
{
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
@@ -25,44 +23,6 @@ public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logge
{
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
_logger.LogError(errorInfo);
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = "MassTransit 自动触发邮件",
Api = "",
Message = $"异常信息:{exception.Message} ",
Stack = exception.StackTrace,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"MassTransit里发送企业微信出现错误:{ex.Message}");
}
}
}
@@ -5,7 +5,6 @@ using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using MassTransit;
using Microsoft.Extensions.Options;
using MimeKit;
@@ -27,7 +26,6 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public class UrgentIRUnReadTaskRecurringEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<Trial> _trialRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
@@ -37,15 +35,9 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<UrgentIRUnReadTaskRecurringEvent> context)
{
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var trialId = context.Message.TrialId;
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var scenario = EmailBusinessScenario.ExpeditedReading;
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
@@ -74,17 +66,13 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
t.UserRole.IdentityUser.EMail,
t.UserRole.IdentityUser.UserName,
t.Trial.TrialCode,
t.Trial.ResearchProgramNo,
t.UserRole.IdentityUser.UserWorkLanguage
t.Trial.ResearchProgramNo
//TrialReadingCriterionList = t.Trial.TrialReadingCriterionList.Select(t => new { t.CriterionName, TrialReadingCriterionId = t.Id }).ToList()
});
foreach (var trialUser in trialUserList)
{
//重新设置当前发送邮件的语言
var workLanguage = trialUser.UserWorkLanguage;
isEn_US = workLanguage == UserWorkLanguage.US;
var userId = trialUser.UserId;
@@ -157,7 +145,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, emailContent, workLanguage),
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, emailContent),
trialUser.FullName,
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
@@ -171,9 +159,9 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc, workLanguage);
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendTrialEmailAsync(messageToSend, trialInfo);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
//处理标记已通知的任务
@@ -18,7 +18,7 @@ public static class OldRecurringEmailHelper
public static string EmailNamePlaceholder => StaticData.EmailSend.EmailNamePlaceholder;
//发送项目邮件的通用方法 已经处理
public static async Task SendTrialEmailAsync(
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<Trial> _trialRepository,
@@ -28,13 +28,12 @@ public static class OldRecurringEmailHelper
Guid trialId, EmailBusinessScenario businessScenario,
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc,
Guid? trialSiteId = null)
Guid? trialSiteId = null, Guid? trialReadingCriterionId = null)
{
//找到配置
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();
var trialInfo=await _trialRepository.Where(t=>t.Id== trialId).FirstOrDefaultAsync();
if (trialEmailConfig == null || trialEmailConfig.IsAutoSend == false || trialEmailConfig.IsEnable == false)
{
@@ -163,7 +162,7 @@ public static class OldRecurringEmailHelper
if (sendEmailConfig != null)
{
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
}

Some files were not shown because too many files have changed in this diff Show More