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
421 changed files with 5215 additions and 1226304 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);
}
}
}
}
-45
View File
@@ -1,45 +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="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="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.USEast1,
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.USEast1,
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.USEast1,
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.USEast1,
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.USEast1,
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": "*"
}
+4 -4
View File
@@ -14,12 +14,12 @@
<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.4" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.4" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.4" />
<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.3" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
+21 -148
View File
@@ -21,8 +21,6 @@ using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using FellowOakDicom.IO.Buffer;
namespace IRaCIS.Core.SCP.Service
{
@@ -41,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; }
@@ -166,48 +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();
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>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _ImageUploadList.Count;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
await _SCPImageUploadRepository.AddAsync(_upload, true);
//可能是测试echo 导致记录了
await _SCPImageUploadRepository.AddAsync(_upload, _upload.FileCount > 0 ? true : false);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
@@ -273,11 +248,6 @@ namespace IRaCIS.Core.SCP.Service
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
else
{
//记录日志
await AddUploadLogAsync();
}
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
@@ -288,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());
@@ -324,70 +280,11 @@ namespace IRaCIS.Core.SCP.Service
long fileSize = 0;
try
{
using (MemoryStream ms = new MemoryStream())
{
await request.File.SaveAsync(ms);
#region 1便
// 回到开头,读取 dicom
ms.Position = 0;
var dicomFile = DicomFile.Open(ms);
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
var syntax = pixelData.Syntax;
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
int fragmentSize = 20 * 1024;
if (syntax.IsEncapsulated)
{
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
for (int n = 0; n < pixelData.NumberOfFrames; n++)
{
var frameData = pixelData.GetFrame(n); // 获取完整一帧
var data = frameData.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;
}
}
// 替换原 PixelData
dicomFile.Dataset.AddOrUpdate(newFragments);
// 重新保存 dicom 到流
ms.SetLength(0);
dicomFile.Save(ms);
}
ms.Position = 0;
#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
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
@@ -410,8 +307,12 @@ namespace IRaCIS.Core.SCP.Service
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.File, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
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);
@@ -440,40 +341,12 @@ namespace IRaCIS.Core.SCP.Service
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;
}
}
}
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++;
}
}
}
+2 -96
View File
@@ -13,7 +13,6 @@ using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Core.SCP.Service
{
@@ -53,10 +52,8 @@ 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);
@@ -85,7 +82,7 @@ namespace IRaCIS.Core.SCP.Service
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;
@@ -154,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)
@@ -196,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),
@@ -220,12 +188,6 @@ namespace IRaCIS.Core.SCP.Service
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, 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,
@@ -239,19 +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.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)
@@ -269,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),
@@ -287,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)
{
@@ -341,21 +271,6 @@ namespace IRaCIS.Core.SCP.Service
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,
@@ -365,15 +280,6 @@ namespace IRaCIS.Core.SCP.Service
++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)
{
@@ -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);
}
}
-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
@@ -126,7 +126,7 @@ namespace IRaCIS.Api.Controllers
var token = _tokenService.GetToken(new UserTokenInfo()
{
IdentityUserId = Guid.NewGuid(),
UserName = "ImageShare",
UserName = "Share001",
UserTypeEnum = UserTypeEnum.ShareImage,
});
@@ -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;
@@ -35,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>
/// 获取稽查数据
@@ -78,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)
@@ -95,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)
@@ -114,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)
@@ -133,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)
@@ -152,7 +126,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[UnitOfWork]
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
@@ -169,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)
@@ -187,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)
@@ -205,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)
@@ -223,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)
@@ -240,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)
@@ -258,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)
@@ -276,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)
@@ -293,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)
@@ -310,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)
@@ -329,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)
{
@@ -371,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;
@@ -384,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;
@@ -400,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)
{
@@ -417,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)
{
@@ -435,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)
{
@@ -450,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)
{
@@ -491,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)
{
@@ -506,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)
{
@@ -522,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)
{
@@ -538,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)
{
@@ -555,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);
@@ -27,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;
@@ -39,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;
@@ -273,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
{
@@ -522,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 });
}
@@ -540,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);
@@ -568,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>
@@ -576,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;
@@ -598,11 +582,9 @@ namespace IRaCIS.Core.API.Controllers
templateFileStream.Seek(0, SeekOrigin.Begin);
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
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;
@@ -748,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
{
@@ -772,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"]);
}
@@ -785,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();
@@ -833,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();
@@ -856,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);
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 });
@@ -871,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中 每一行的 中心编号,姓名,邮箱,用户类型数据记录完整再进行上传
@@ -919,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())))
@@ -1004,11 +954,7 @@ namespace IRaCIS.Core.API.Controllers
EmailBodyHtml = 4,
ReadKeyFile = 5,
Other = 6,
Other = 5
}
/// <summary>
@@ -1040,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));
@@ -41,22 +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}");
}
}
// 项目手动选择,周期性邮件
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();
@@ -82,7 +67,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
}
+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>
+1 -2
View File
@@ -318,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>
-2
View File
@@ -1,7 +1,6 @@
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;
@@ -101,7 +100,6 @@ builder.Services.AddControllers(options =>
options.Filters.Add<UnitOfWorkFilter>();
options.Filters.Add<LimitUserRequestAuthorization>();
options.Filters.Add<TrialGlobalLimitActionFilter>();
options.Filters.Add<RequestDuplicationFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
-1
View File
@@ -1,3 +1,2 @@
{
}
@@ -126,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属性注入需要
@@ -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:60);
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
#region
@@ -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();
// }
@@ -27,8 +27,7 @@ namespace IRaCIS.Core.API
hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions()
{
SchemaName = "dbo",
}).UseRecommendedSerializerSettings();
// 移除 UseSimpleAssemblyNameTypeSerializer() 以避免类型加载问题
}).UseRecommendedSerializerSettings().UseSimpleAssemblyNameTypeSerializer();
}
+7 -16
View File
@@ -16,11 +16,11 @@
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
"BucketName": "tl-med-irc-event-store",
"ViewEndpoint": "https://tl-med-irc-event-store.oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tNRTsqL6aWmHkDmTwoH",
"AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH",
"RoleArn": "acs:ram::1899121822495495:role/irc-oss-access",
"BucketName": "zy-irc-store",
"ViewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
@@ -55,14 +55,11 @@
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"FromEmail": "uat@extimaging.com",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
@@ -72,13 +69,7 @@
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+7 -17
View File
@@ -7,10 +7,10 @@
}
},
"ConnectionStrings": {
"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"
//"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"
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
@@ -53,17 +53,14 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"FromEmail": "irc@extimaging.com",
"FromName": "IRC Imaging System",
"FromName": "irc",
"AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login",
"SystemShortName": "IRC",
@@ -75,18 +72,11 @@
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.193.237"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+23 -98
View File
@@ -1,179 +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"
},
// 对象存储服务配置
"ObjectStoreService": {
// 使用的对象存储服务类型
"ObjectStoreUse": "AliyunOSS",
// 阿里云对象存储服务的配置
"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": {
// 打开用户复杂密码
"OpenUserComplexPassword": false,
// 是否在开始工作前强制签署电子知情同意书
"OpenSignDocumentBeforeWork": false,
// 是否启用登录失败次数限制(防暴力破解)
"OpenLoginLimit": false,
// 连续登录失败多少次后触发锁定
"LoginMaxFailCount": 5,
// 触发锁定后账号锁定时长(分钟)
"LoginFailLockMinutes": 1,
// 无操作自动登出的超时时间(分钟)
"AutoLoginOutMinutes": 10,
// 是否启用多因子登录认证(MFA)
"OpenLoginMFA": false,
// 连续阅片的最长工作时间(分钟),超时后强制休息
"ContinuousReadingTimeMin": 120,
// 强制休息时长(分钟)
"ReadingRestTimeMin": 10,
// 是否强制用户定期修改密码
"IsNeedChangePassWord": true,
// 密码有效期(天),到期后必须修改
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
// 是否打开项目关联删除
"OpenTrialRelationDelete": true,
// 转换PDF服务配置
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf",
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
// 邮件服务配置(用于系统通知、找回密码、错误报警等)
"SystemEmailSendConfig": {
// SMTP端口
"Port": 465,
// 企业邮箱SMTP服务器地址
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
// 发件人邮箱地址
"OpenTrialRelationDelete": true,
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
},
"SystemEmailSendConfig": {
"Port": 465,
"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": "Extlmaging",
// 组织中文名称
"OrganizationNameCN": "Extlmaging",
// 公司英文全称
"CompanyName": "Extensive Imaging",
// 公司中文全称
"CompanyNameCN": "上海展影医疗科技有限公司",
// 公司英文简称
"CompanyShortName": "Extensive Imaging",
// 公司中文简称
"CompanyShortNameCN": "展影医疗",
// 是否为国际版环境
"IsEnv_US": false,
// 是否开启系统异常邮件报警
"IsOpenErrorNoticeEmail": false,
// 邮箱格式校验正则表达式
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
// 接收系统异常报警的邮箱列表
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
"ErrorNoticeEmailList": ["872297557@qq.com"]
},
// PACS 连接配置
"SystemPacsConfig": {
// PACS服务器端口
"Port": "11113",
// PACS服务器IP地址
"IP": "106.14.89.110"
},
// 重复请求配置
"RequestDuplicationOptions": {
// 是否启用重复请求检测
"IsEnabled": true,
// 重复请求时间窗口(毫秒)
"DuplicationWindowMs": 200,
// 缓存请求时间(秒)
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
@@ -75,7 +75,6 @@
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"FromEmail": "test@extimaging.com",
"FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021",
@@ -92,12 +91,5 @@
"SystemPacsConfig": {
"Port": "11113",
"IP": "106.14.89.110"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+2 -13
View File
@@ -57,18 +57,15 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenTrialRelationDelete": false,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"OpenTrialRelationDelete": false
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
@@ -81,20 +78,12 @@
"SiteUrl": "https://lili.elevateimaging.ai/login",
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "44.210.231.169"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+2 -12
View File
@@ -66,17 +66,14 @@
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenLoginMFA": true,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"OpenLoginMFA": true
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
@@ -95,13 +92,6 @@
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+3 -13
View File
@@ -65,17 +65,14 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"TemplateType": 1
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
@@ -88,19 +85,12 @@
"SiteUrl": "https://lili.uat.elevateimaging.ai/login",
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+2 -13
View File
@@ -72,17 +72,14 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyDays": 1
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login",
@@ -95,20 +92,12 @@
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.253.119"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
-7
View File
@@ -74,12 +74,5 @@
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
"scope": "repo"
}
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
@@ -1,137 +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 (Exception)
{
}
}
var resultContext = await next();
}
/// <summary>
///
/// </summary>
private void RequestDuplication()
{
var requestPath = accessor?.HttpContext?.Request?.Path.ToString() ?? string.Empty;
// 验证请求频繁情况
if (
!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
};
IRCSystemInfo.RequestRecordList= IRCSystemInfo.RequestRecordList.Where(x => x.RequestTime >= DateTime.Now.AddSeconds(-RequestDuplicationOptionsMonitor.CurrentValue.CacheTimeSeconds)).ToList();
var requestsTimes = IRCSystemInfo.RequestRecordList.Any(x=>
x.RequestTime>= requestInfo.RequestTime.AddMilliseconds(-RequestDuplicationOptionsMonitor.CurrentValue.DuplicationWindowMs)&&
x.RequestKey== requestInfo.RequestKey);
if (requestsTimes)
{
throw new BusinessValidationFailedException(_localizer["RequestDuplicationFilter_RequestDuplication"], ApiResponseCodeEnum.BusinessValidationFailed);
}
IRCSystemInfo.RequestRecordList.Add(requestInfo);
}
}
public string GenerateParameterHash(string parameters)
{
if (string.IsNullOrEmpty(parameters))
return string.Empty;
using var sha256 = SHA256.Create();
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(parameters));
return Convert.ToBase64String(hashBytes);
}
}
}
}
@@ -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;
@@ -38,8 +38,6 @@ public class ServiceVerifyConfigOption
public string ThirdPdfUrl { get; set; }
public int UserMFAVerifyMinutes { get; set; } = 1440;
}
public class SystemEmailSendConfig
@@ -47,8 +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 string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
@@ -57,7 +53,7 @@ public class SystemEmailSendConfig
public string SiteUrl { 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;
@@ -74,14 +70,12 @@ public class SystemEmailSendConfig
public bool IsOpenErrorNoticeEmail { get; set; }
public string EmailRegexStr { 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;
@@ -89,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
@@ -109,54 +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>();
}
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; }
-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;
@@ -61,9 +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 class CacheHelper
@@ -1,190 +0,0 @@
using FellowOakDicom;
using FellowOakDicom.Media;
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 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 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(), Path.GetFileName(filename));
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;
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
}
//清理临时文件
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);
#endregion
}
}
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;
}
}
}
@@ -42,7 +42,6 @@ public static class SendEmailHelper
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
@@ -55,20 +54,6 @@ public static class SendEmailHelper
}
/// <summary>
/// 设置发送邮箱
/// </summary>
/// <param name="messageToSend"></param>
/// <param name="senderName"></param>
public static void ChangeEmailSenderName(MimeMessage messageToSend, string senderName)
{
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
if (fromAddress != null)
{
messageToSend.From.Clear();
messageToSend.From.Add(new MailboxAddress(senderName, fromAddress.Address));
}
}
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
@@ -243,7 +243,6 @@ public static class ExcelExportHelper
}
public class DynamicColumnConfig
{
/// <summary>
@@ -261,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 填充列数据
@@ -304,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;
@@ -494,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);
@@ -551,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);
@@ -572,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);
}
}
@@ -585,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);
@@ -593,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);
@@ -624,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)
@@ -661,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;
@@ -925,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);
@@ -1038,34 +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 memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, tplPath, 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>
@@ -1147,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)
{
@@ -83,21 +83,15 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateSystemCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
{
switch (businessScenario)
{
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
break;
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
Console.WriteLine("更新项目到期job");
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron);
break;
default:
break;
+17 -143
View File
@@ -18,7 +18,6 @@ using Minio.DataModel.Args;
using Minio.Exceptions;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Web;
namespace IRaCIS.Core.Application.Helper;
@@ -112,8 +111,6 @@ public class AliyunOSSTempToken
public string PreviewEndpoint { get; set; }
public string DownloadEndPoint => EndPoint.Insert(EndPoint.IndexOf("//") + 2, BucketName + ".");
}
[LowerCamelCaseJson]
@@ -126,7 +123,7 @@ public class AWSTempToken
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public DateTime? Expiration { get; set; }
public DateTime Expiration { get; set; }
}
public enum ObjectStoreUse
@@ -143,12 +140,10 @@ public enum ObjectStoreUse
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, bool randomFileName = false);
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
public Task<Stream> GetStreamFromOSSAsync(string ossRelativePath);
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
public Task<string> GetSignedUrl(string ossRelativePath);
@@ -194,7 +189,7 @@ public class OSSService : IOSSService
/// <returns></returns>
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
{
BackBatchGetToken();
GetObjectStoreTempToken();
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
@@ -284,37 +279,6 @@ public class OSSService : IOSSService
}
//后端批量上传 或者下载,不每个文件获取临时token
private void BackBatchGetToken()
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
if (AliyunOSSTempToken == null)
{
GetObjectStoreTempToken();
}
//token 过期了
if (AliyunOSSTempToken?.Expiration.AddSeconds(10) <= DateTime.Now)
{
GetObjectStoreTempToken();
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
if (AWSTempToken == null)
{
GetObjectStoreTempToken();
}
//token 过期了
if (AWSTempToken.Expiration?.AddSeconds(10) <= DateTime.Now)
{
GetObjectStoreTempToken();
}
}
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
@@ -322,22 +286,16 @@ public class OSSService : IOSSService
/// <param name="localFilePath"></param>
/// <param name="oosFolderPath"></param>
/// <param name="isFileNameAddGuid"></param>
/// <param name="randomFileName">随机文件名</param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true, bool randomFileName = false)
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
{
BackBatchGetToken();
GetObjectStoreTempToken();
var localFileName = Path.GetFileName(localFilePath);
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
if (randomFileName)
{
var fileExtension = localFileName.Split(".").LastOrDefault();
ossRelativePath = $"{oosFolderPath}/{Guid.NewGuid()}.{fileExtension}";
}
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
@@ -402,7 +360,7 @@ public class OSSService : IOSSService
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
BackBatchGetToken();
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
@@ -415,12 +373,14 @@ public class OSSService : IOSSService
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))
{
await result.Content.CopyToAsync(fs);
result.Content.CopyTo(fs);
fs.Close();
}
}
@@ -484,96 +444,6 @@ public class OSSService : IOSSService
}
public async Task<Stream> GetStreamFromOSSAsync(string ossRelativePath)
{
BackBatchGetToken();
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);
// 将OSS返回的流复制到内存流中并返回
var memoryStream = new MemoryStream();
await result.Content.CopyToAsync(memoryStream);
memoryStream.Position = 0; // 重置位置以便读取
return 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 memoryStream = new MemoryStream();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithCallbackStream(stream => stream.CopyToAsync(memoryStream));
await minioClient.GetObjectAsync(getObjectArgs);
memoryStream.Position = 0;
return memoryStream;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(
AWSTempToken.AccessKeyId,
AWSTempToken.SecretAccessKey,
AWSTempToken.SessionToken
);
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var getObjectRequest = new Amazon.S3.Model.GetObjectRequest
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath
};
var response = await amazonS3Client.GetObjectAsync(getObjectRequest);
var memoryStream = new MemoryStream();
await response.ResponseStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss流获取失败! " + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
GetObjectStoreTempToken();
@@ -1130,9 +1000,14 @@ public class OSSService : IOSSService
}
}
private bool isFirstCall = true;
public async Task<long> GetObjectSizeAsync(string sourcePath)
{
BackBatchGetToken();
if (isFirstCall)
{
GetObjectStoreTempToken();
isFirstCall = false;
}
var objectkey = sourcePath.Trim('/');
@@ -1143,10 +1018,9 @@ public class OSSService : IOSSService
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
var key = HttpUtility.UrlDecode(objectkey);
var metadata = _ossClient.GetObjectMetadata(aliConfig.BucketName, key);
var metadata = _ossClient.GetObjectMetadata(aliConfig.BucketName, objectkey);
long fileSize = metadata?.ContentLength ?? 0; // 文件大小(字节)
long fileSize = metadata.ContentLength; // 文件大小(字节)
return fileSize;
}
@@ -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.1.6" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.1.3" />
<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.4.1" />
<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="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.1.0" />
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.2" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.2" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.4" />
<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.3.0" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="MimeKit" Version="4.11.0" />
<PackageReference Include="MiniExcel" Version="1.41.2" />
<PackageReference Include="Minio" Version="6.0.3" />
<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="9.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="112.1.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<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,20 +17,13 @@ namespace IRaCIS.Core.Application.MassTransit.Command
public class CheckDBModel : CheckViewModel
{
public Guid SubjectVisitId { get; set; }
public Guid StudyId { 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; }
}
//[ExcelImporter(/*ImportResultFilter = typeof(ImportResultFilteTest),*/ IsLabelingError = true)]
@@ -82,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)
{
@@ -1,49 +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.Domain.Models;
using IRaCIS.Core.Domain.Share;
using MassTransit;
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.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;
}
@@ -63,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
@@ -113,7 +117,7 @@ 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))
{
@@ -197,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)
@@ -297,167 +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.AsQueryable() 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,
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,
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");
//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();
}
}
}
@@ -266,43 +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).ToListAsync();
// CRC和CRA单独取
var crcAndcraUserList = await _trialSiteRepository.Where(x => x.Id == inDto.SubjectVisit.TrialSiteId).SelectMany(x => x.CRCUserList.Select(y => y.UserRole)).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<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)
@@ -365,11 +379,8 @@ public class ImageConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -503,7 +514,6 @@ public class ImageConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -127,7 +127,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -235,7 +235,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -353,7 +353,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -462,7 +462,7 @@ public class UrgentIRApplyedReReadingConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -121,7 +121,7 @@ public class UserSiteSurveySubmitedEventConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -204,7 +204,7 @@ public class SiteSurveySPMSubmitedEventConsumer(
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -318,7 +318,7 @@ public class SiteSurverRejectedEventConsumer(
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -121,7 +121,7 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -220,7 +220,7 @@ public class CRCRepliedQCChallengeEventConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -338,7 +338,7 @@ public class QCRepliedQCChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -438,7 +438,7 @@ public class CRCRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -548,7 +548,7 @@ public class PMRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -647,7 +647,7 @@ public class CheckStateChangedToAuditEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -756,7 +756,7 @@ public class QCClaimTaskEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
@@ -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,
@@ -40,7 +38,6 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
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();
@@ -163,7 +160,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
//处理标记已通知的任务
@@ -28,10 +28,10 @@ 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();
@@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace IRaCIS.Core.Application.MassTransit.Consumer;
//项目手动选择 周期性邮件
/// <summary>
@@ -58,24 +58,3 @@ public class SystemDocumentPublishEvent : DomainEvent
}
/// <summary>
/// 定时提醒
/// </summary>
public class TrialDocumentErverDayEvent : DomainEvent
{
}
public class TrialDocumentPublishEvent : DomainEvent
{
public List<Guid> Ids { get; set; }
/// <summary>
/// 新增的需要发送邮件的用户角色ID列表
/// 如果为null或空,则发送给所有相关角色
/// </summary>
public List<Guid> NewUserTypeIds { get; set; }
}
@@ -114,7 +114,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -1,304 +0,0 @@
using DocumentFormat.OpenXml;
using IRaCIS.Application.Contracts;
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.DependencyInjection;
using Microsoft.Extensions.Options;
using MimeKit;
using NPOI.SS.Formula.Functions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Joins;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.MassTransit.Recurring
{
/// <summary>
/// 定时过期提醒
/// </summary>
public class TrialDocumentErverDayEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<SystemDocument> _systemDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<SystemDocConfirmedIdentityUser> _systemDocConfirmedUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository, IRepository<TrialDocument> _trialDocumentRepository,
IRepository<Trial> _trialRepository,
ISystemDocumentService _systemDocumentService,
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
IRepository<TrialDocNeedConfirmedUserType> _trialDocNeedConfirmedUserTypeRepository,
IServiceScopeFactory serviceScopeFactory,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<TrialDocConfirmedIdentityUser> _trialDocConfirmedUserRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentErverDayEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<TrialDocumentErverDayEvent> context)
{
DateTime now = DateTime.Now;
Console.WriteLine("发送定时项目过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(true)
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
join confirm in _trialDocConfirmedUserRepository.Where() on
new { trialIdentityUser.IdentityUserId, TrialDocumentId = trialDoc.Id } equals new { IdentityUserId = confirm.ConfirmUserId, confirm.TrialDocumentId } into cc
from confirm in cc.DefaultIfEmpty()
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new TrialSignDocView()
{
TrialCode = trialDoc.Trial.TrialCode,
ResearchProgramNo = trialDoc.Trial.ResearchProgramNo,
ExperimentName = trialDoc.Trial.ExperimentName,
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
NewStaffTrainDays = trialDoc.NewStaffTrainDays,
Id = trialDoc.Id,
IsSystemDoc = false,
CreateTime = trialDoc.CreateTime,
FullFilePath = trialDoc.Path,
IsDeleted = trialDoc.IsDeleted,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
//IsConfirmed = confirm.ConfirmTime != null,
ConfirmUserId = identityUser.Id,
ConfirmTime = confirm.ConfirmTime,
RealName = trialIdentityUser.IdentityUser.FullName,
UserName = trialIdentityUser.IdentityUser.UserName,
UserCreateTime= trialIdentityUser.CreateTime,
IdentityUserTypeList = trialIdentityUser.TrialUserRoleList.Select(t => t.UserRole.UserTypeRole.UserTypeShortName).ToList(),
DocNeedSignUserTypeList = trialDoc.NeedConfirmedUserTypeList.Select(t => t.UserTypeRole.UserTypeShortName).ToList(),
};
var datalist = await trialDocQuery.IgnoreQueryFilters().Where(t => t.IsDeleted == false && t.ConfirmTime == null&&t.ConfirmTime==null)
.ToListAsync();
datalist = datalist.Where(x => x.SuggestFinishTime != null && x.SuggestFinishTime.Value.Date == DateTime.Now.Date)
.ToList();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
Console.WriteLine("发送定时项目过期提醒:人员数量" + userinfoList.Count);
int index = 1;
foreach (var userinfo in userinfoList)
{
try
{
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
index++;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
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 topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.UserName, // 用户名 {0}
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
var scenario = EmailBusinessScenario.TrialTraining_ExpirationNotification;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
catch (Exception)
{
}
}
}
}
/// <summary>
/// 生效通知
/// </summary>
public class TrialDocumentPublishEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialDocument> _trialDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig) : IConsumer<TrialDocumentPublishEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<TrialDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
{
Console.WriteLine($"只发送给新增项目的角色,角色数量: {context.Message.NewUserTypeIds.Count}");
}
// 构建查询
IQueryable<UnionDocumentWithConfirmInfoView> systemDocQuery;
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
{
// 只查询新增角色的用户
systemDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x=>x.IsDeleted==false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && context.Message.NewUserTypeIds.Contains(ur.UserRole.UserTypeId) && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = true,
Id = trialDoc.Id,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
ConfirmUserId = identityUser.Id,
RealName = identityUser.FullName,
UserName = identityUser.UserName,
IsNeedSendEmial = identityUser.IsZhiZhun,
FullFilePath = trialDoc.Path
};
}
else
{
// 查询所有相关角色的用户
systemDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x=>x.IsDeleted==false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted &&trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = false,
Id = trialDoc.Id,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
Name = trialDoc.Name,
Path = trialDoc.Path,
FileTypeId = trialDoc.FileTypeId,
UpdateTime = trialDoc.UpdateTime,
ConfirmUserId = identityUser.Id,
RealName = identityUser.FullName,
UserName = identityUser.UserName,
IsNeedSendEmial = identityUser.IsZhiZhun ,
FullFilePath = trialDoc.Path
};
}
var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync();
int index = 1;
foreach (var userinfo in userinfoList)
{
string msg = $"{index}项目生效通知,邮箱:{userinfo.EMail},姓名{userinfo.UserName},";
index++;
try
{
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
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 topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(
CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr),
userinfo.UserName, // 用户名 {0}
_systemEmailConfig.SiteUrl
);
return (topicStr, htmlBodyStr);
};
var scenario = EmailBusinessScenario.TrialTraining_EffectiveNotification;
var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsEnable).FirstOrDefault();
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
msg += "发送成功";
}
}
catch (Exception)
{
msg += "发送失败";
}
Console.WriteLine(msg);
}
}
}
}
@@ -71,8 +71,6 @@ namespace IRaCIS.Core.Application.ViewModel
public bool IsPMSetBack { get; set; }
public bool IsSubjectQuit { get; set; }
#region
public Guid TrialReadingCriterionId { get; set; }
@@ -240,7 +238,6 @@ namespace IRaCIS.Core.Application.ViewModel
//public string ReReadingOriginalTaskCode { get; set; }
public string ApplicantName { get; set; }
public Guid Id { get; set; }
@@ -490,8 +487,6 @@ namespace IRaCIS.Core.Application.ViewModel
public int? RandomOrder { get; set; }
public bool? IsRandomOrderList { get; set; }
public CriterionType? CriterionType { get; set; }
}
@@ -48,7 +48,7 @@ namespace IRaCIS.Core.Application.Service
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
public async Task<IResponseOutput> AddOrUpdateTaskAllocationRule(TaskAllocationRuleAddOrEdit addOrEditTaskAllocationRule)
{
@@ -79,7 +79,7 @@ namespace IRaCIS.Core.Application.Service
}
[TrialGlobalLimit("AfterStopCannNotOpt")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
[HttpDelete("{taskAllocationRuleId:guid}")]
public async Task<IResponseOutput> DeleteTaskAllocationRule(Guid taskAllocationRuleId)
@@ -125,7 +125,7 @@ namespace IRaCIS.Core.Application.Service
[HttpGet("{trialId:guid}")]
public async Task<List<TrialDoctorUserSelectView>> GetDoctorUserSelectList(Guid trialId, [FromServices] IRepository<Enroll> _enrollRepository)
{
var query = from enroll in _enrollRepository.Where(t => t.TrialId == trialId /*&& t.TaskAllocationRule.IsEnable*/ && /*(t.EnrollStatus >= EnrollStatus.ConfirmIntoGroup ||*/ t.Trial.SubjectDoctorUserList.Any(v => v.DoctorUserId == t.DoctorUser.Id))
var query = from enroll in _enrollRepository.Where(t => t.TrialId == trialId && t.EnrollStatus >= EnrollStatus.ConfirmIntoGroup)
join user in _userRoleRepository.AsQueryable() on enroll.DoctorId equals user.DoctorId
select new TrialDoctorUserSelectView()
{
@@ -213,7 +213,7 @@ namespace IRaCIS.Core.Application.Service
//为防止脏数据 这里也多判断一次
var existCurrentVisitTaskList = _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId
&& t.TrialReadingCriterionId == trialReadingCriterionConfig.TrialReadingCriterionId && t.TaskState == TaskState.Effect
&& t.SourceSubjectVisitId == subjectVisit.Id && t.IsAnalysisCreate == false).ToList();
&& t.SourceSubjectVisitId == subjectVisit.Id).ToList();
if (trialReadingCriterionConfig.ReadingType == ReadingMethod.Double)
{
@@ -682,7 +682,7 @@ namespace IRaCIS.Core.Application.Service
var existCurrentVisitTaskList = _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId
&& t.TrialReadingCriterionId == trialReadingCriterionId && t.TaskState == TaskState.Effect
/* && t.TaskAllocationState == TaskAllocationState.NotAllocate && t.DoctorUserId == null*/ && t.SourceSubjectVisitId == subjectVisit.Id && t.IsAnalysisCreate == false).ToList();
/* && t.TaskAllocationState == TaskAllocationState.NotAllocate && t.DoctorUserId == null*/ && t.SourceSubjectVisitId == subjectVisit.Id).ToList();
VisitTask? task1 = existCurrentVisitTaskList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm1);
VisitTask? task2 = existCurrentVisitTaskList.FirstOrDefault(t => t.ArmEnum == Arm.DoubleReadingArm2);
@@ -870,7 +870,7 @@ namespace IRaCIS.Core.Application.Service
//有可能仅仅只分配了一个Subject 未分配 那么
if (assignConfigList.Any(t => t.ArmEnum == Arm.DoubleReadingArm1) && task1 != null)
{
{
arm1.TaskAllocationState = TaskAllocationState.Allocated;
arm1.AllocateTime = DateTime.Now;
arm1.DoctorUserId = task1.DoctorUserId;
@@ -903,7 +903,7 @@ namespace IRaCIS.Core.Application.Service
BlindTrialSiteCode = latestTask.BlindTrialSiteCode,
IsAnalysisCreate = latestTask.IsAnalysisCreate,
IsSelfAnalysis = latestTask.IsSelfAnalysis,
TrialReadingCriterionId = latestTask.TrialReadingCriterionId,
IsNeedClinicalDataSign = latestTask.IsNeedClinicalDataSign,
IsClinicalDataSign = latestTask.IsClinicalDataSign
@@ -913,10 +913,10 @@ namespace IRaCIS.Core.Application.Service
{
taskOne.TaskAllocationState = TaskAllocationState.Allocated;
taskOne.AllocateTime = DateTime.Now;
taskOne.DoctorUserId = task1!.DoctorUserId;
taskOne.SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget);
taskOne. DoctorUserId = task1!.DoctorUserId;
taskOne. SuggesteFinishedTime = GetSuggessFinishTime(true, UrgentType.NotUrget);
}
currentMaxCodeInt = currentMaxCodeInt + 1;
@@ -976,7 +976,7 @@ namespace IRaCIS.Core.Application.Service
BlindTrialSiteCode = latestTask.BlindTrialSiteCode,
IsAnalysisCreate = latestTask.IsAnalysisCreate,
IsSelfAnalysis = latestTask.IsSelfAnalysis,
TrialReadingCriterionId = latestTask.TrialReadingCriterionId,
IsNeedClinicalDataSign = latestTask.IsNeedClinicalDataSign,
IsClinicalDataSign = latestTask.IsClinicalDataSign
@@ -1084,7 +1084,7 @@ namespace IRaCIS.Core.Application.Service
//该Subject 之前是否有已分配的 如果改变配置 可能会出现 一个Subject 分配的同一个医生 有的在Arm1 有的在Arm2
var allocateSubjectArmList = _visitTaskRepository.Where(t => t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialId == trialId && t.DoctorUserId != null && t.ArmEnum != Arm.JudgeArm && t.IsAnalysisCreate == false)
var allocateSubjectArmList = _visitTaskRepository.Where(t => t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialId == trialId && t.DoctorUserId != null && t.ArmEnum != Arm.JudgeArm)
.Select(t => new { t.DoctorUserId, t.ArmEnum }).Distinct().ToList();
//不是初次分配
@@ -1105,7 +1105,7 @@ namespace IRaCIS.Core.Application.Service
if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder)
{
//之前有回退到影像上传的访视 那么当前访视一致性核查通过的时候,当前访视生成但是不分配出去(排除失访的)
var beforeBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum < subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false && t.IsAnalysisCreate == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
var beforeBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum < subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
if (beforeBackVisitTask == null)
@@ -1130,7 +1130,7 @@ namespace IRaCIS.Core.Application.Service
#endregion
var followBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum > subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false && t.IsAnalysisCreate == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
var followBackVisitTask = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.SubjectId == subjectVisit.SubjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.VisitTaskNum > subjectVisit.VisitNum && t.ReadingCategory == ReadingCategory.Visit && t.SourceSubjectVisit.CheckState != CheckStateEnum.CVPassed && t.SourceSubjectVisit.IsLostVisit == false).OrderBy(t => t.VisitTaskNum).FirstOrDefaultAsync();
//存在退回访视1 又退回基线 这种情况 生成任务 考虑基线先一致性核查通过,但是访视1还未通过时 生成任务
var followVisitTaskList = await _visitTaskRepository
@@ -1409,34 +1409,30 @@ namespace IRaCIS.Core.Application.Service
.Where(x => x.ClinicalDataTrialSet.UploadRole == UploadRole.PM || x.FileCount > 0)
.Include(t => t.ReadingClinicalDataPDFList).Include(t => t.ClinicalDataTrialSet).ToList();
//防止多标准重复插入
if (!_readingConsistentClinicalDataRepository.Any(t => t.SubjectId == subjectId))
foreach (var clinicalData in clinicalDataList)
{
foreach (var clinicalData in clinicalDataList)
var consistnentClinicalData = _mapper.Map<ReadingConsistentClinicalData>(clinicalData);
var id = NewId.NextSequentialGuid();
consistnentClinicalData.Id = id;
if (consistnentClinicalData.ClinicalDataTrialSet.ClinicalUploadType == ClinicalUploadType.PDF)
{
var consistnentClinicalData = _mapper.Map<ReadingConsistentClinicalData>(clinicalData);
var id = NewId.NextSequentialGuid();
consistnentClinicalData.Id = id;
if (consistnentClinicalData.ClinicalDataTrialSet.ClinicalUploadType == ClinicalUploadType.PDF)
{
consistnentClinicalData.IsSign = false;
consistnentClinicalData.IsBlind = false;
consistnentClinicalData.IsComplete = true;
consistnentClinicalData.ReadingClinicalDataState = ReadingClinicalDataStatus.HaveUploaded;
consistnentClinicalData.ClinicalDataTrialSet = null;
}
var consistanClinicalDataPdfList = _mapper.Map<List<ReadingConsistentClinicalDataPDF>>(clinicalData.ReadingClinicalDataPDFList);
consistanClinicalDataPdfList.ForEach(t => { t.ReadingConsistentClinicalDataId = id; t.Id = Guid.Empty; });
consistnentClinicalData.ReadingClinicalDataPDFList = consistanClinicalDataPdfList;
await _readingConsistentClinicalDataRepository.AddAsync(consistnentClinicalData);
consistnentClinicalData.IsSign = false;
consistnentClinicalData.IsBlind = false;
consistnentClinicalData.IsComplete = true;
consistnentClinicalData.ReadingClinicalDataState = ReadingClinicalDataStatus.HaveUploaded;
consistnentClinicalData.ClinicalDataTrialSet = null;
}
}
var consistanClinicalDataPdfList = _mapper.Map<List<ReadingConsistentClinicalDataPDF>>(clinicalData.ReadingClinicalDataPDFList);
consistanClinicalDataPdfList.ForEach(t => { t.ReadingConsistentClinicalDataId = id; t.Id = Guid.Empty; });
consistnentClinicalData.ReadingClinicalDataPDFList = consistanClinicalDataPdfList;
await _readingConsistentClinicalDataRepository.AddAsync(consistnentClinicalData);
}
foreach (var task in generateTaskCommand.GenerataConsistentTaskList)
{
@@ -46,7 +46,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
IRepository<SubjectCanceDoctor> _subjectCanceDoctorRepository,
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
//IRepository<ReadingCustomTag> _readingCustomTagRepository,
IRepository<ReadingCustomTag> _readingCustomTagRepository,
IRepository<TaskInfluence> _taskInfluenceRepository,
IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository,
ILogger<VisitTaskService> _logger,
@@ -764,7 +764,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
.WhereIf(inQuery.BeginSignTime != null, t => t.SignTime > inQuery.BeginSignTime)
.WhereIf(inQuery.EndSignTime != null, t => t.SignTime < inQuery.EndSignTime)
.WhereIf(inQuery.RandomOrder != null, t => t.RandomOrder == inQuery.RandomOrder)
.WhereIf(inQuery.IsRandomOrderList == true, t => (t.ReadingTaskState == ReadingTaskState.WaitReading || t.ReadingTaskState == ReadingTaskState.Reading) && t.TaskAllocationState == TaskAllocationState.Allocated)
.WhereIf(inQuery.IsRandomOrderList == true, t => (t.ReadingTaskState == ReadingTaskState.WaitReading || t.ReadingTaskState==ReadingTaskState.Reading) && t.TaskAllocationState==TaskAllocationState.Allocated)
.ProjectTo<ReadingTaskView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(ReadingTaskView.IsUrgent) + " desc", nameof(ReadingTaskView.SubjectCode), nameof(ReadingTaskView.VisitTaskNum) };
@@ -965,7 +965,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//随机阅片
else
{
var taskQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId && x.Subject.IsSubjectQuit == false)
var taskQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId)
.Where(x => !x.Subject.IsDeleted).Where(x => (x.IsNeedClinicalDataSign && x.IsClinicalDataSign) || !x.IsNeedClinicalDataSign);
iRUnReadOut = new IRUnReadOutDto()
@@ -1013,7 +1013,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var extralConfig = JsonConvert.DeserializeObject<TrialExtraConfig>(extralObj?.TrialExtraConfigJsonStr) ?? new TrialExtraConfig();
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect && x.Subject.IsSubjectQuit == false)
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
//前序 不存在 未生成任务的访视
@@ -1404,12 +1404,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
task.ReReadingApplyState = ReReadingApplyState.TrialGroupHaveApplyed;
if (!isSPMjoin)
{
task.ReReadingApplyState = ReReadingApplyState.Agree;
task.PMBackReason = applyReReadingCommand.RequestReReadingReason;
}
}
@@ -1621,7 +1615,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var origenalTask = (await _visitTaskRepository.Where(t => item.OriginalReReadingTaskId == t.Id).FirstOrDefaultAsync()).IfNullThrowException();
if ((origenalTask.TaskState != TaskState.Effect && origenalTask.TaskState != TaskState.Freeze))
{
await _visitTaskReReadingRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new VisitTaskReReading() { RequestReReadingConfirmUserId = _userInfo.UserRoleId, RequestReReadingResultEnum = RequestReReadingResult.Invalid });
//---当前申请重阅任务的状态,已被其他任务重阅已影响,不允许对该状态下的任务进行重阅同意与否操作
throw new BusinessValidationFailedException(_localizer["VisitTask_ReapplyStatusConflict"]);
}
//更新申请信息
var visitTaskReReadingAppply = await _visitTaskReReadingRepository.FirstOrDefaultAsync(t => t.Id == item.Id);
@@ -1634,13 +1634,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
if (agreeReReadingCommand.RequestReReadingResultEnum == RequestReReadingResult.Agree)
{
if ((origenalTask.TaskState != TaskState.Effect && origenalTask.TaskState != TaskState.Freeze))
{
await _visitTaskReReadingRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new VisitTaskReReading() { RequestReReadingConfirmUserId = _userInfo.UserRoleId, RequestReReadingResultEnum = RequestReReadingResult.Invalid });
//---当前申请重阅任务的状态,已被其他任务重阅已影响,不允许对该状态下的任务进行重阅同意与否操作
throw new BusinessValidationFailedException(_localizer["VisitTask_ReapplyStatusConflict"]);
}
await AgreeReReading(origenalTask.Id, visitTaskReReadingAppply.RequestReReadingType, visitTaskReReadingAppply);
@@ -1717,12 +1710,12 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
if (criterionConfig.CriterionType == CriterionType.RECIST1Point1 && criterionConfig.IsAdditionalAssessment)
{
// PM申请 SPM / CPM审批
//if (requestReReadingType == RequestReReadingType.TrialGroupApply)
//{
// filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
//}
if (requestReReadingType == RequestReReadingType.TrialGroupApply)
{
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
}
//IR 申请 PM审批
if (requestReReadingType != RequestReReadingType.TrialGroupApply)
else
{
// 1.1 基线任务影响BM任务
@@ -1741,11 +1734,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
else
{
if (requestReReadingType != RequestReReadingType.TrialGroupApply)
{
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId);
}
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == origenalTask.TrialReadingCriterionId);
}
//PM申请 SPM / CPM审批 回退访视,因此这里不生成访视任务 影响多个标准的任务
@@ -2216,16 +2206,16 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
{
//自定义
//var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
//foreach (var item in readingCustomTagList)
//{
// item.Id = Guid.Empty;
// item.VisitTaskId = newTask.Id;
// item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
//}
foreach (var item in readingCustomTagList)
{
item.Id = Guid.Empty;
item.VisitTaskId = newTask.Id;
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
}
//_ = _readingCustomTagRepository.AddRangeAsync(readingCustomTagList).Result;
_ = _readingCustomTagRepository.AddRangeAsync(readingCustomTagList).Result;
@@ -2596,7 +2586,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
sv.CurrentActionUserId = null;
sv.PreliminaryAuditUserId = null;
sv.ReviewAuditUserId = null;
sv.SecondReviewState = SecondReviewState.None;
if (sv.IsBaseLine)
{
@@ -2646,7 +2636,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
/// <param name="applyId"> 申请记录的Id</param>
/// <returns></returns>
[HttpGet("{taskId:guid}/{isReReading:bool}")]
public async Task<IResponseOutput<List<InfluenceTaskInfo>>> GetReReadingOrBackInfluenceTaskList(Guid taskId, bool isReReading, Guid? applyId)
public async Task<(List<InfluenceTaskInfo>, object)> GetReReadingOrBackInfluenceTaskList(Guid taskId, bool isReReading, Guid? applyId)
{
var isIRAppyTaskInfluenced = false;
@@ -2664,16 +2654,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//IR 申请1.1 基线重阅,影响附加评估所有的任务
var isIR1Point1AdditionalAssessmentBaseline = false;
//是IR 申请 PM 审批流程
var isIRApplyPMApprovalProcess = (IsPMOrAPm() && applyId != null && await _visitTaskReReadingRepository.AnyAsync(t => t.Id == applyId && t.CreateUserRole.UserTypeEnum == UserTypeEnum.IndependentReviewer))
|| (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IndependentReviewer && applyId == null);
//附加评估 IR 和PM 看到的影响列表不一样
if (criterionConfig.CriterionType == CriterionType.RECIST1Point1 && criterionConfig.IsAdditionalAssessment)
{
// IR 申请 PM 同意
if (isIRApplyPMApprovalProcess)
if (((IsPMOrAPm() && applyId != null && await _visitTaskReReadingRepository.AnyAsync(t => t.Id == applyId && t.CreateUserRole.UserTypeEnum == UserTypeEnum.IndependentReviewer))
|| (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IndependentReviewer && applyId == null)))
{
// 1.1 基线任务影响BM任务
@@ -2692,20 +2679,18 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
}
//(1、PM回退,PM申请重阅,SPM同意回退)--20250804-感觉这里没用,不用限制
//else
//{
// filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
//(1、PM回退,PM申请重阅,SPM同意回退)
else
{
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId || t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB);
//}
}
}
else
{
if (isReReading == true && isIRApplyPMApprovalProcess)
{
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId);
}
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId);
}
@@ -2946,8 +2931,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
#endregion
//IsIRAppyTaskInfluenced 列表中存在IR已申请重阅的任务!
return ResponseOutput.Ok(list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
return (list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
}
@@ -3029,7 +3014,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
else
{
if (!_visitTaskRepository.Any(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId && t.DoctorUserId == inCommand.DoctorUserId
if(!_visitTaskRepository.Any(t => t.TrialId == inCommand.TrialId && t.TrialReadingCriterionId == inCommand.TrialReadingCriterionId && t.DoctorUserId == inCommand.DoctorUserId
&& t.TaskAllocationState == TaskAllocationState.Allocated && t.ReadingTaskState == ReadingTaskState.WaitReading && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze)
&& t.RandomOrder == item.RandomOrder))
{
@@ -2,7 +2,6 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore.Migrations;
namespace IRaCIS.Core.Application.Service
{
@@ -118,9 +117,7 @@ namespace IRaCIS.Core.Application.Service
.ForMember(o => o.IsReadingShowPreviousResults, t => t.MapFrom(u => u.TrialReadingCriterion.IsReadingShowPreviousResults))
.ForMember(o => o.DigitPlaces, t => t.MapFrom(u => u.TrialReadingCriterion.DigitPlaces))
.ForMember(o => o.IseCRFShowInDicomReading, t => t.MapFrom(u => u.TrialReadingCriterion.IseCRFShowInDicomReading))
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.TrialReadingCriterion.CriterionType))
.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.Subject.IsSubjectQuit))
;
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.TrialReadingCriterion.CriterionType));
CreateMap<VisitTask, VisitTaskView>().IncludeBase<VisitTask, VisitTaskViewBasic>()
@@ -163,10 +160,8 @@ namespace IRaCIS.Core.Application.Service
CreateMap<VisitTaskReReading, ReReadingTaskView>()
.ForMember(o => o.ApplicantName, t => t.MapFrom(u => u.CreateUserRole.IdentityUser.FullName))
.ForMember(o => o.ReReadingNewTaskCode, t => t.MapFrom(u => u.NewReReadingTask.TaskCode))
.ForMember(o => o.OriginalReReadingTask, t => t.MapFrom(u => u.OriginalReReadingTask))
//.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.OriginalReReadingTask.Subject.IsSubjectQuit))
;
@@ -226,7 +221,6 @@ namespace IRaCIS.Core.Application.Service
.ForMember(o => o.IseCRFShowInDicomReading, t => t.MapFrom(u => u.VisitTask.TrialReadingCriterion.IseCRFShowInDicomReading))
.ForMember(o => o.CriterionType, t => t.MapFrom(u => u.VisitTask.TrialReadingCriterion.CriterionType))
.ForMember(o => o.IsSubjectQuit, t => t.MapFrom(u => u.VisitTask.Subject.IsSubjectQuit))
.ForMember(o => o.Id, t => t.MapFrom(u => u.Id))
.ForMember(o => o.MedicalNo, t => t.MapFrom(u => u.VisitTask.Subject.MedicalNo))
.ForMember(o => o.DoctorUser, t => t.MapFrom(u => u.VisitTask.DoctorUser))
@@ -16,7 +16,7 @@ namespace IRaCIS.Core.Application.Service
/// 系统模板文档配置表
/// </summary>
[ApiExplorerSettings(GroupName = "Common")]
public class CommonDocumentService(IRepository<CommonDocument> _commonDocumentRepository,
public class CommonDocumentService(IRepository<CommonDocument> _commonDocumentRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IWebHostEnvironment _hostEnvironment) : BaseService, ICommonDocumentService
{
@@ -36,14 +36,6 @@ namespace IRaCIS.Core.Application.Service
return await commonDocumentQueryable.ToPagedListAsync(queryCommonDocument);
}
[HttpGet]
public async Task<CommonDocument> GetCommonDocument(string code)
{
var find = await _commonDocumentRepository.Where(t => t.Code == code).FirstOrDefaultAsync();
return find;
}
public async Task<IResponseOutput> AddOrUpdateCommonDocument(CommonDocumentAddOrEdit addOrEditCommonDocument)
{
@@ -39,10 +39,6 @@ namespace IRaCIS.Application.Contracts
public string ValueCN { get; set; } = string.Empty;
}
public class ChildInQuery:SortInput
{
public Guid ParentId { get; set; }
}
public class AddOrEditBasicDic
{
@@ -1,156 +0,0 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:47Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Share;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel;
public class EmailLogView : EmailLogAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public List<EmailRecipientLogView> RecipientList { get; set; }
public List<EmaliAttachmentInfo> AttachmentList { get; set; }
}
public class EmaliAttachmentInfo
{
/// <summary>
/// 附件名称
/// </summary>
public string AttachmentName { get; set; }
/// <summary>
/// 附件路径
/// </summary>
public string AttachmentPath { get; set; }
}
public class EmailRecipientLogView
{
public Guid Id { get; set; }
/// <summary>
/// 邮件Id
/// </summary>
public Guid EmailLogId { get; set; }
/// <summary>
/// 收件人姓名
/// </summary>
public string RecipientName { get; set; }
/// <summary>
/// 收件人地址
/// </summary>
public string RecipientAddress { get; set; }
/// <summary>
/// 收件人类型
/// </summary>
public RecipientType RecipientTypeEnum { get; set; }
/// <summary>
/// 排序
/// </summary>
public int Sort { get; set; }
}
public class EmailLogAddOrEdit
{
public Guid? Id { get; set; }
/// <summary>
/// 邮件Id
/// </summary>
public string MessageId { get; set; }
/// <summary>
/// 唯一Id
/// </summary>
public string UniqueId { get; set; }
/// <summary>
/// 邮件主题
/// </summary>
public string EmailSubject { get; set; } = string.Empty;
/// <summary>
/// 日期
/// </summary>
public DateTime? EmailDate { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string ErrorInfo { get; set; }
/// <summary>
/// 发件人地址
/// </summary>
public string SenderAddress { get; set; }
/// <summary>
/// 发件人姓名
/// </summary>
public string SenderName { get; set; }
/// <summary>
/// 邮件内容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 邮件状态
/// </summary>
public EmailState EmailStateEnum { get; set; }
}
public class GetEmailInfoOutDto:EmailLogView
{
}
public class GetEmailInfoInDto
{
public Guid Id { get; set; }
}
public class ResendEmailInDto
{
public Guid Id { get; set; }
}
public class EmailLogQuery:PageInput
{
public Guid? TrialId { get; set; }
public DateTime? EmailStartDate { get; set; }
public DateTime? EmailEndDate { get; set; }
public EmailState? EmailStateEnum { get; set; }
public string ToRecipientName { get; set; } = string.Empty;
public string CcRecipientName { get; set; } = string.Empty;
}
@@ -96,6 +96,9 @@ namespace IRaCIS.Core.Application.Contracts
public bool IsDeleted { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
/// <summary> 业务模块 /// </summary>
public int BusinessModuleEnum { get; set; }
@@ -137,24 +140,6 @@ namespace IRaCIS.Core.Application.Contracts
public int? EmailDelaySeconds { get; set; }
[Comment("邮件配置的多个标准")]
public List<CriterionType>? CriterionTypeList { get; set; }
//public CriterionType? CriterionTypeEnum { get; set; }
}
public class BatchUpdateEmailTopicCommand
{
[NotDefault]
public Guid Id { get; set; }
public string EmailTopic { get; set; } = string.Empty;
public string EmailTopicCN { get; set; } = string.Empty;
}
@@ -3,7 +3,6 @@
// 生成时间 2022-03-28 16:43:52
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Wordprocessing;
using IRaCIS.Core.Infra.EFCore.Common;
using System.ComponentModel.DataAnnotations;
@@ -64,26 +63,6 @@ namespace IRaCIS.Core.Application.ViewModel
}
public class ModuleTypeData
{
public bool IsShow { get; set; }
public Guid Id { get; set; }
public Guid? ParentId { get; set; }
public Guid DictionaryId { get; set; }
public int ShowOrder { get; set; }
public string DictionaryValue { get; set; }
}
public class GetModuleTypeListInDto
{
public Guid TrialId { get; set; }
}
/// <summary>
/// 复制其他对象到当前对象
/// </summary>
@@ -159,12 +138,6 @@ namespace IRaCIS.Core.Application.ViewModel
public class FrontAuditConfigAddOrEdit
{
public Guid? Id { get; set; }
/// <summary>
/// 适用的标准
/// </summary>
public List<CriterionType> ApplyCriterionList { get; set; } = new List<CriterionType>() { };
public string Value { get; set; } = string.Empty;
public string ValueCN { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
@@ -175,11 +148,6 @@ namespace IRaCIS.Core.Application.ViewModel
public Guid UpdateUserId { get; set; }
public string Code { get; set; } = string.Empty;
/// <summary>
/// 是否默认选择
/// </summary>
public bool IsDefaultChoice { get; set; } = false;
/// <summary>
/// 字段的英文值
/// </summary>
@@ -335,7 +303,7 @@ namespace IRaCIS.Core.Application.ViewModel
public bool IsConfig { get; set; }
public string DictionaryKey { get; set; } = string.Empty;
public bool IsBeforeModifyView { get; set; } = true;
//byzhouahng
@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.Common.DTO
{
//public class MailModel
//{
//}
}
@@ -182,12 +182,13 @@ namespace IRaCIS.Core.Application.Service
/// <summary>
/// 获取子项数组
/// </summary>
/// <param name="parentId"></param>
/// <returns></returns>
[HttpPost]
public async Task<List<BasicDicView>> GetChildList(ChildInQuery inQuery)
[HttpGet("{parentId:guid}")]
public async Task<List<BasicDicView>> GetChildList(Guid parentId)
{
return await _dicRepository.Where(t => t.ParentId == inQuery.ParentId)
.ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).SortToListAsync(inQuery);
return await _dicRepository.Where(t => t.ParentId == parentId)
.OrderBy(t => t.ShowOrder).ProjectTo<BasicDicView>(_mapper.ConfigurationProvider).ToListAsync();
}
@@ -1,695 +0,0 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:42Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using IdentityModel.Client;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MimeKit;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service;
/// <summary>
/// 邮件日志
/// </summary>
/// <param name="_emailLogRepository"></param>
/// <param name="systemEmailConfig"></param>
/// <param name="_mapper"></param>
/// <param name="_userInfo"></param>
/// <param name="_localizer"></param>
[ApiExplorerSettings(GroupName = "Common")]
public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
IRepository<Trial> _trialRepository,
IRepository<EmailAttachmentLog> _emailAttachmentLogRepository,
IOSSService oSSService,
IRepository<EmailRecipientLog> _emailRecipientLogRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
/// <summary>
/// 获取邮件日志列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inDto)
{
var emailFromName = await _trialRepository.Where(x=>x.Id==inDto.TrialId).Select(x=>x.EmailFromName).FirstOrDefaultAsync();
if (emailFromName.IsNullOrEmpty())
{
emailFromName = _systemEmailConfig.FromName;
}
var emailLogQueryable = _emailLogRepository
.Where(x=>x.SenderName== emailFromName)
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(),x=>x.EmailRecipientLogList.Any(x=>x.RecipientTypeEnum==RecipientType.Cc&&x.RecipientName.Contains(inDto.CcRecipientName)))
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
return pageList;
}
/// <summary>
/// 获取单条邮件日志详情
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<GetEmailInfoOutDto> GetEmailInfo(GetEmailInfoInDto inDto)
{
var emailInfo=await _emailLogRepository
.Where(x => x.Id == inDto.Id).Include(x=>x.EmailRecipientLogList)
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider).AsNoTracking()
.FirstNotNullAsync();
if (emailInfo.UniqueId.IsNotNullOrEmpty())
{
using (var client = new ImapClient())
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
if (emailInfo.AttachmentList.Count == 0)
{
List< EmaliAttachmentInfo > attachmentInfos = new List<EmaliAttachmentInfo>();
foreach (var att in message.Attachments)
{
EmaliAttachmentInfo emaliAttachmentInfo = new EmaliAttachmentInfo();
emaliAttachmentInfo.AttachmentName = att.ContentDisposition?.FileName ?? att.ContentType.Name ?? "unknown";
// 2. 解码后的流直接上传,不落盘
if (att is MimePart part)
{
// 重要:每次上传新建一个独立流,否则迭代过程中流位置会乱
await using var decodeStream = new MemoryStream();
await part.Content.DecodeToAsync(decodeStream);
decodeStream.Position = 0;
emaliAttachmentInfo.AttachmentPath = await oSSService.UploadToOSSAsync(
fileStream: decodeStream,
oosFolderPath: $"EmailAttachment/{emailInfo.Id}", // OSS 虚拟目录
fileRealName: emaliAttachmentInfo.AttachmentName,
isFileNameAddGuid: true); // 让方法自己在文件名前加 Guid
attachmentInfos.Add(emaliAttachmentInfo);
}
}
List<EmailAttachmentLog> emailAttachmentLog = attachmentInfos.Select(x => new EmailAttachmentLog()
{
EmailLogId = emailInfo.Id.Value,
AttachmentName = x.AttachmentName,
AttachmentPath = x.AttachmentPath,
}).ToList();
await _emailAttachmentLogRepository.AddRangeAsync(emailAttachmentLog);
await _emailAttachmentLogRepository.SaveChangesAsync();
emailInfo.AttachmentList = attachmentInfos;
}
sentFolder.Close();
}
catch (Exception ex)
{
}
finally
{
client.Disconnect(true);
}
}
}
//var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
return emailInfo;
}
/// <summary>
/// 重发邮件
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> ResendEmail(ResendEmailInDto inDto)
{
var emailInfo = await _emailLogRepository
.Where(x => x.Id == inDto.Id)
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider)
.FirstNotNullAsync();
if (emailInfo.UniqueId.IsNotNullOrEmpty())
{
using (var client = new ImapClient())
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
sentFolder.Close();
}
catch (Exception ex)
{
}
finally
{
client.Disconnect(true);
}
}
}
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(emailInfo.SenderName, emailInfo.SenderAddress));
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.To))
{
messageToSend.To.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
}
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.Cc))
{
messageToSend.Cc.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
}
messageToSend.Subject = emailInfo.EmailSubject;
var builder = new BodyBuilder();
builder.HtmlBody = emailInfo.Content;
messageToSend.Body = builder.ToMessageBody();
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
return ResponseOutput.Ok();
}
/// <summary>
/// 同步邮件
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> SynchronizationEmail()
{
var maxTime=await _emailLogRepository.MaxAsync(t => t.EmailDate);
List<EmailLog> emailList = new List<EmailLog>();
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
using (var client = new ImapClient())
{
try
{
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
AuthenticateImap(client);
var sentFolder = OpenSentFolder(client);
var startDate = maxTime ?? DateTime.MinValue;
var searchQuery = SearchQuery.All.And(SearchQuery.DeliveredAfter(startDate));
var uids = sentFolder.Search(searchQuery).ToList();
foreach (var uid in uids)
{
try
{
//var message = sentFolder.GetMessage(uid);
// var emailView = ConvertToEmailLogView(uid, message);
var message = sentFolder.GetMessage(uid);
if (message.Date.DateTime <= startDate)
{
continue;
}
var emaillog = new EmailLog
{
Id=NewId.NextGuid(),
UniqueId = uid.ToString(),
MessageId = message.MessageId ?? string.Empty,
EmailSubject = message.Subject ?? string.Empty,
EmailDate = message.Date.DateTime,
EmailStateEnum = EmailState.Success,
};
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
if (fromMailbox != null)
{
emaillog.SenderAddress = fromMailbox.Address;
emaillog.SenderName = fromMailbox.Name ?? string.Empty;
}
List<EmailRecipientLog> recipientLogs= new List<EmailRecipientLog>();
int sort = 0;
message.To.Mailboxes.ForEach(x =>
{
recipientLogs.Add(new EmailRecipientLog()
{
RecipientName = x.Name ?? string.Empty,
RecipientAddress = x.Address,
EmailLogId = emaillog.Id,
RecipientTypeEnum = RecipientType.To,
Sort = sort++,
});
});
sort = 0;
message.Cc.Mailboxes.ForEach(x =>
{
recipientLogs.Add(new EmailRecipientLog()
{
RecipientName = x.Name ?? string.Empty,
RecipientAddress = x.Address,
EmailLogId = emaillog.Id,
RecipientTypeEnum = RecipientType.Cc,
Sort = sort++,
});
});
emailList.Add(emaillog);
EmailRecipientLogList.AddRange(recipientLogs);
}
catch (Exception ex)
{
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
}
}
sentFolder.Close();
}
catch (Exception ex)
{
}
finally
{
client.Disconnect(true);
}
await _emailLogRepository.AddRangeAsync(emailList);
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
await _emailLogRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
}
private IMailFolder OpenSentFolder(ImapClient client)
{
IMailFolder folder = null;
try
{
folder = client.GetFolder(SpecialFolder.Sent);
}
catch { }
if (folder == null)
{
var candidates = new[] { "已发送", "已发送邮件", "Sent", "Sent Items", "[Gmail]/Sent Mail" };
foreach (var name in candidates)
{
try
{
var f = client.GetFolder(name);
if (f != null)
{
folder = f;
break;
}
}
catch { }
}
}
if (folder == null)
{
try
{
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
if (personal != null)
{
foreach (var sub in personal.GetSubfolders(false))
{
if (string.Equals(sub.FullName, "Sent", StringComparison.OrdinalIgnoreCase) ||
string.Equals(sub.FullName, "Sent Items", StringComparison.OrdinalIgnoreCase))
{
folder = sub;
break;
}
}
}
}
catch { }
}
if (folder == null)
throw new InvalidOperationException("未找到已发送文件夹");
folder.Open(FolderAccess.ReadOnly);
return folder;
}
private void AuthenticateImap(ImapClient client)
{
try
{
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
}
catch (AuthenticationException)
{
//if (_systemEmailConfig.UseOAuth2 && _systemEmailConfig.OAuth2AccessToken.IsNotNullOrEmpty())
//{
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, _systemEmailConfig.OAuth2AccessToken);
// client.Authenticate(sasl);
// return;
//}
//if (_systemEmailConfig.OAuth2AccessToken.IsNullOrEmpty())
//{
// var token = AcquireOAuth2TokenByPassword();
// if (token.IsNotNullOrEmpty())
// {
// _systemEmailConfig.OAuth2AccessToken = token;
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, token);
// client.Authenticate(sasl);
// return;
// }
//}
throw;
}
}
//private string AcquireOAuth2TokenByPassword()
//{
// if (_systemEmailConfig.OAuthTenantId.IsNullOrEmpty() || _systemEmailConfig.OAuthClientId.IsNullOrEmpty())
// return string.Empty;
// using var http = new HttpClient();
// var req = new PasswordTokenRequest
// {
// Address = $"https://login.microsoftonline.com/{_systemEmailConfig.OAuthTenantId}/oauth2/v2.0/token",
// ClientId = _systemEmailConfig.OAuthClientId,
// ClientSecret = _systemEmailConfig.OAuthClientSecret,
// Scope = "offline_access https://outlook.office365.com/IMAP.AccessAsUser.All",
// UserName = _systemEmailConfig.FromEmail,
// Password = _systemEmailConfig.AuthorizationCode
// };
// var resp = http.RequestPasswordTokenAsync(req).GetAwaiter().GetResult();
// if (resp.IsError || resp.AccessToken.IsNullOrEmpty())
// return string.Empty;
// return resp.AccessToken;
//}
// 取skip的值
public int CalcReverseSkip(int pageIndex, int pageSize, int totalCount)
{
int lastPageFullSkip = totalCount / pageSize * pageSize; // 最后一页“整页”起点
int skip = lastPageFullSkip - pageIndex * pageSize; // 倒着减
return Math.Max(skip, 0); // 防止负值
}
/// <summary>
/// 邮件筛选条件构建
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
private SearchQuery BuildSearchQuery(EmailLogQuery query)
{
var searchQuery = SearchQuery.All;
// 日期范围筛选
if (query.EmailStartDate.HasValue || query.EmailEndDate.HasValue)
{
var startDate = query.EmailStartDate ?? DateTime.MinValue;
var endDate = query.EmailEndDate ?? DateTime.MaxValue;
searchQuery = searchQuery.And(SearchQuery.DeliveredAfter(startDate)
.And(SearchQuery.DeliveredBefore(endDate.AddDays(1)))); // 包含结束日期当天
}
// 主题关键词筛选(如果IMAP服务器支持)
if (!string.IsNullOrEmpty(query.ToRecipientName))
{
searchQuery = searchQuery.And(SearchQuery.HeaderContains("To", query.ToRecipientName));
}
if (!string.IsNullOrEmpty(query.CcRecipientName))
{
searchQuery = searchQuery.And(SearchQuery.HeaderContains("Cc", query.CcRecipientName));
}
return searchQuery;
}
/// <summary>
/// 邮件排序处理
/// </summary>
/// <param name="sentFolder"></param>
/// <param name="uids"></param>
/// <returns></returns>
private List<EmailLogView> GetEmailsWithSorting(IMailFolder sentFolder, IList<UniqueId> uids)
{
var emailList = new List<EmailLogView>();
// 获取完整的邮件信息
foreach (var uid in uids)
{
try
{
var message = sentFolder.GetMessage(uid);
var emailView = ConvertToEmailLogView(uid, message);
emailList.Add(emailView);
}
catch (Exception ex)
{
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
}
}
return emailList;
}
/// <summary>
/// 获取完整的邮件信息
/// </summary>
/// <param name="uid"></param>
/// <param name="message"></param>
/// <returns></returns>
private EmailLogView ConvertToEmailLogView(UniqueId uid, MimeMessage message)
{
string saveFolder = @"D:\MailAttachments";
Directory.CreateDirectory(saveFolder);
var emailView = new EmailLogView
{
UniqueId = uid.ToString(),
MessageId = message.MessageId ?? string.Empty,
EmailSubject = message.Subject ?? string.Empty,
EmailDate = message.Date.UtcDateTime,
Content = message.HtmlBody ?? string.Empty,
};
var toHeader = message.Headers[HeaderId.To]; // 原始头,未解码
Console.WriteLine($"原始To头 = |{toHeader}|");
foreach (var att in message.Attachments)
{
// att 可能是 MimePart 或 MessagePart
if (att is MimePart part && !string.IsNullOrEmpty(part.FileName))
{
var file = Path.Combine(saveFolder, part.FileName);
using (var stream = File.Create(file))
part.Content.DecodeTo(stream);
Console.WriteLine($" 附件已保存:{file}");
}
}
// 处理发件人信息
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
if (fromMailbox != null)
{
emailView.SenderAddress = fromMailbox.Address;
emailView.SenderName = fromMailbox.Name ?? string.Empty;
}
//// 处理收件人
//emailView.ToRecipients = message.To.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
//// 处理抄送人
//emailView.CcRecipients = message.Cc.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
//// 处理密送人
//emailView.BccRecipients = message.Bcc.Mailboxes.Select(address => new EmaliSendInfo
//{
// Name = address.Name ?? string.Empty,
// Address = address.Address
//}).ToList();
// 处理附件
//emailView.Attachments = ProcessAttachments(message);
//emailView.HasAttachments = emailView.Attachments.Any();
//emailView.AttachmentCount = emailView.Attachments.Count;
return emailView;
}
///// <summary>
///// 处理附件信息
///// </summary>
///// <param name="message"></param>
///// <returns></returns>
//private List<EmailAttachmentInfo> ProcessAttachments(MimeMessage message)
//{
// var attachments = new List<EmailAttachmentInfo>();
// foreach (var attachment in message.Attachments)
// {
// var attachmentInfo = new EmailAttachmentInfo();
// if (attachment is MimePart mimePart)
// {
// attachmentInfo.FileName = mimePart.FileName ??
// mimePart.ContentType?.Name ??
// "未知文件";
// attachmentInfo.ContentType = mimePart.ContentType.MimeType;
// attachmentInfo.ContentId = mimePart.ContentId ?? string.Empty;
// attachmentInfo.IsInline = mimePart.ContentDisposition != null &&
// mimePart.ContentDisposition.Disposition.Equals("inline", StringComparison.OrdinalIgnoreCase);
// // 获取附件大小
// try
// {
// if (mimePart.ContentObject.Stream != null)
// {
// attachmentInfo.Size = mimePart.ContentObject.Stream.Length;
// }
// }
// catch
// {
// attachmentInfo.Size = 0;
// }
// }
// else if (attachment is MessagePart messagePart)
// {
// // 处理内嵌消息作为附件的情况
// attachmentInfo.FileName = "embedded-message.eml";
// attachmentInfo.ContentType = "message/rfc822";
// attachmentInfo.IsInline = false;
// }
// attachments.Add(attachmentInfo);
// }
// return attachments;
//}
/// <summary>
/// 应用内存中的过滤条件(对于无法通过IMAP搜索的条件)
/// </summary>
/// <param name="emails"></param>
/// <param name="query"></param>
/// <returns></returns>
private List<EmailLogView> ApplyMemoryFilters(List<EmailLogView> emails, EmailLogQuery query)
{
var filtered = emails.AsEnumerable();
// 收件人姓名筛选
//if (!string.IsNullOrEmpty(query.Keyword))
//{
// filtered = filtered.Where(e =>
// e.ToRecipients.Any(r =>
// r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase)) ||
// e.CcRecipients.Any(r =>
// r.Name.Contains(query.Keyword, StringComparison.OrdinalIgnoreCase))
// );
//}
// 发件人姓名筛选
//if (!string.IsNullOrEmpty(query.SenderName))
//{
// filtered = filtered.Where(e =>
// e.SenderName.Contains(query.SenderName, StringComparison.OrdinalIgnoreCase));
//}
return filtered.ToList();
}
public async Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog)
{
var entity = await _emailLogRepository.InsertOrUpdateAsync(addOrEditEmailLog, true);
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{emailLogId:guid}")]
public async Task<IResponseOutput> DeleteEmailLog(Guid emailLogId)
{
var success = await _emailLogRepository.DeleteFromQueryAsync(t => t.Id == emailLogId,true);
return ResponseOutput.Ok();
}
}
@@ -4,18 +4,9 @@
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Spreadsheet;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using NPOI.SS.Formula.Functions;
using System.Text.RegularExpressions;
@@ -26,75 +17,8 @@ namespace IRaCIS.Core.Application.Contracts
/// </summary>
[ApiExplorerSettings(GroupName = "Common")]
public class EmailNoticeConfigService(IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
/// <summary>
/// 获取邮件列表
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<object> GetEmailList()
{
List<IMessageSummary> emailList = new List<IMessageSummary>();
using (var client = new ImapClient())
{
try
{
// 连接阿里邮箱 IMAP 服务器(使用 SSL)
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
// 登录
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
// 3. 获取发件箱文件夹 - 使用你找到的“已发送”
var sentFolder = client.GetFolder("已发送");
sentFolder.Open(FolderAccess.ReadOnly);
// 4. 搜索所有邮件(你可以在这里添加更精确的搜索条件)
var uids = sentFolder.Search(SearchQuery.All);
Console.WriteLine($"找到 {uids.Count} 封已发送邮件");
// 5. 遍历并处理邮件(示例中处理前10封)
foreach (var uid in uids.Take(10))
{
var message = sentFolder.GetMessage(uid);
// 输出邮件基本信息
Console.WriteLine($"主题: {message.Subject}");
Console.WriteLine($"收件人: {string.Join(", ", message.To.Mailboxes.Select(m => m.Address))}");
Console.WriteLine($"日期: {message.Date.LocalDateTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"发件人: {message.From}");
Console.WriteLine("----------------------------------");
}
sentFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}
finally
{
client.Disconnect(true);
}
}
return true;
}
[HttpPost]
public async Task<PageOutput<EmailNoticeConfigView>> GetEmailNoticeConfigList(EmailNoticeConfigQuery inQuery)
@@ -104,7 +28,7 @@ namespace IRaCIS.Core.Application.Contracts
.WhereIf(inQuery.SystemLevel != null, t => t.SystemLevel == inQuery.SystemLevel)
.WhereIf(inQuery.IsDistinguishCriteria != null, t => t.IsDistinguishCriteria == inQuery.IsDistinguishCriteria)
.WhereIf(inQuery.BusinessLevelEnum != null, t => t.BusinessLevelEnum == inQuery.BusinessLevelEnum)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeList.Any(t=>t==inQuery.CriterionTypeEnum))
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeEnum == inQuery.CriterionTypeEnum)
.WhereIf(inQuery.BusinessModuleEnum != null, t => t.BusinessModuleEnum == inQuery.BusinessModuleEnum)
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
.WhereIf(inQuery.IsReturnRequired != null, t => t.IsReturnRequired == inQuery.IsReturnRequired)
@@ -117,77 +41,16 @@ namespace IRaCIS.Core.Application.Contracts
return await emailNoticeConfigQueryable.ToPagedListAsync(inQuery);
}
/// <summary>
/// 批量更新邮件主题中英文
/// </summary>
/// <param name="inCommandList"></param>
/// <returns></returns>
public async Task<IResponseOutput> BatchUpdateEmail(List<BatchUpdateEmailTopicCommand> inCommandList)
{
var findIdList = inCommandList.Select(x => x.Id).ToList();
var regex = new Regex(@"\{\s*\d+\s*\}");
foreach (var inCommand in inCommandList)
{
if (regex.Matches($"{inCommand.EmailTopic}{inCommand.EmailTopicCN}")
.Any(t => t.Value.Contains(" ")))
{
//邮件模板占位符不允许有空格,请核查占位符的地方
return ResponseOutput.NotOk(I18n.T("EmailNoticeConfig_ContainEmpty"));
}
}
var list = _emailNoticeConfigrepository.Where(t => findIdList.Contains(t.Id), true).ToList();
foreach (var item in list)
{
var exist = inCommandList.FirstOrDefault(t => t.Id == item.Id);
if (exist != null)
{
item.EmailTopic = exist.EmailTopic;
item.EmailTopicCN = exist.EmailTopicCN;
}
}
await _emailNoticeConfigrepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
public async Task<IResponseOutput> AddOrUpdateEmailNoticeConfig(EmailNoticeConfigAddOrEdit addOrEditEmailNoticeConfig)
{
//var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
//{
// VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum == addOrEditEmailNoticeConfig.CriterionTypeEnum,
// VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
//};
var criterionList = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.IsEnable == true)
.Where(t => t.CriterionTypeList != null)
.WhereIf(addOrEditEmailNoticeConfig.Id != null, t => t.Id != addOrEditEmailNoticeConfig.Id)
.Select(t => t.CriterionTypeList).ToList();//不能使用selectMany 会当成关联对象,不能当成字符串
if (addOrEditEmailNoticeConfig.CriterionTypeList != null)
var verifyExp1 = new EntityVerifyExp<EmailNoticeConfig>()
{
foreach (var item in addOrEditEmailNoticeConfig.CriterionTypeList)
{
foreach (var itemList in criterionList)
{
if (itemList.Any(t => t == item))
{
return ResponseOutput.NotOk(_localizer["EmailNoticeConfig_RepeatEmailScenario"]);
}
}
}
}
VerifyExp = t => t.BusinessScenarioEnum == addOrEditEmailNoticeConfig.BusinessScenarioEnum && t.CriterionTypeEnum == addOrEditEmailNoticeConfig.CriterionTypeEnum,
VerifyMsg = _localizer["EmailNoticeConfig_RepeatEmailScenario"]
};
var verifyExp2 = new EntityVerifyExp<EmailNoticeConfig>()
{
@@ -231,7 +94,7 @@ namespace IRaCIS.Core.Application.Contracts
}
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp2);
await _emailNoticeConfigrepository.AddAsync(entity, true, verifyExp1, verifyExp2);
}
@@ -254,7 +117,7 @@ namespace IRaCIS.Core.Application.Contracts
}
entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp2);
entity = await _emailNoticeConfigrepository.UpdateFromDTOAsync(addOrEditEmailNoticeConfig, true, false, verifyExp1, verifyExp2);
@@ -262,16 +125,16 @@ namespace IRaCIS.Core.Application.Contracts
}
if (addOrEditEmailNoticeConfig.EmailCron != string.Empty)
if (entity.EmailCron != string.Empty)
{
var jobId = $"{entity.Id}_({addOrEditEmailNoticeConfig.BusinessScenarioEnum})";
HangfireJobHelper.RemoveCronJob(jobId);
var jobId = $"{entity.Id}_({entity.BusinessScenarioEnum})";
//有的job 可能编辑控制直接不发,需要移除已存在的
HangfireJobHelper.RemoveCronJob(jobId);
if (entity.IsAutoSend && entity.IsEnable)
{
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, addOrEditEmailNoticeConfig.BusinessScenarioEnum, addOrEditEmailNoticeConfig.EmailCron);
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, entity.BusinessScenarioEnum, entity.EmailCron);
}
}
@@ -37,7 +37,6 @@ namespace IRaCIS.Core.Application.Service.Common
IRepository<DicomStudy> _dicomStudyRepository,
IRepository<QCChallenge> _qcChallengeRepository,
IRepository<ReadModule> _readModuleRepository,
IRepository<Trial> _trialRepository,
IRepository<NoneDicomStudy> _noneDicomStudyRepository,
IRepository<StudyMonitor> _studyMonitorRepository,
IRepository<CommonDocument> _commonDocumentRepository,
@@ -48,228 +47,8 @@ namespace IRaCIS.Core.Application.Service.Common
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IWebHostEnvironment _hostEnvironment) : BaseService
{
/// <summary>
/// 质控问题答案导出
/// </summary>
/// <param name="trialId"></param>
/// <param name="_trialQCQuestionAnswerRepository"></param>
/// <param name="_trialRepository"></param>
/// <param name="_dictionaryService"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GetTrialQCQuestionAnserList_Export(GetQCQuestionAnswerQuery inQuery,
[FromServices] IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository,
[FromServices] IRepository<Trial> _trialRepository,
[FromServices] IRepository<UserRole> _userRoleRepository,
[FromServices] IRepository<TrialQCQuestion> _trialQCQuestionRepository,
[FromServices] IDictionaryService _dictionaryService)
{
//var query = from sv in _subjectVisitRepository.Where(t => t.TrialId == trialId && t.AuditState == AuditStateEnum.QCPassed)
// join u in _userRoleRepository.AsQueryable() on sv.UpdateUserId equals u.Id
// select new QCQuestionReusult_export()
// {
// TrialSiteCode = sv.TrialSite.TrialSiteCode,
// SubjectCode = sv.Subject.Code,
// VisitName = sv.VisitName,
// SubjectVisitId = sv.Id,
// CurrentQCEnum = qa.CurrentQCEnum,
// AuditTime = qa.UpdateTime,
// SecondReviewTime = qa.SecondReviewTime,
// AuditUserName = u.UserName,
// AuditUserFullName = u.FullName,
// QuesitonName = qa.TrialQCQuestionConfigure.QuestionName,
// ShowOrder = qa.TrialQCQuestionConfigure.ShowOrder,
// Answer = qa.Answer
// }
var trialId = inQuery.TrialId;
var query = from qa in _trialQCQuestionAnswerRepository.Where(t => t.TrialId == trialId && t.SubjectVisit.IsDeleted == false &&
((t.SubjectVisit.AuditState >= AuditStateEnum.PrimaryQCPassed && t.SecondReviewTime == null) ||
(t.SubjectVisit.SecondReviewState == SecondReviewState.AuditPassed && t.SecondReviewTime <= t.SubjectVisit.Trial.QCQuestionConfirmedTime) ||
(t.SubjectVisit.SecondReviewState == SecondReviewState.WaitAudit && t.SecondReviewTime < t.SubjectVisit.Trial.QCQuestionConfirmedTime)))
join u in _userRoleRepository.AsQueryable() on qa.UpdateUserId equals u.Id
select new QCQuestionAnswerResult_export()
{
SubjectVisitId = qa.SubjectVisitId,
SecondReviewTime = qa.SecondReviewTime,
VisitNum = qa.SubjectVisit.VisitNum,
TrialSiteCode = qa.SubjectVisit.TrialSite.TrialSiteCode,
SubjectCode = qa.SubjectVisit.Subject.Code,
VisitName = qa.SubjectVisit.VisitName,
CurrentQCEnum = qa.CurrentQCEnum,
AuditTime = qa.CurrentQCEnum == CurrentQC.First ? qa.SubjectVisit.PreliminaryAuditTime : (qa.CurrentQCEnum == CurrentQC.Second ? qa.SubjectVisit.ReviewAuditTime : qa.UpdateTime),
AuditUserName = qa.CurrentQCEnum == CurrentQC.First ? qa.SubjectVisit.PreliminaryAuditUser.UserName : (qa.CurrentQCEnum == CurrentQC.Second ? qa.SubjectVisit.ReviewAuditUser.UserName : u.UserName),
AuditUserFullName = qa.CurrentQCEnum == CurrentQC.First ? qa.SubjectVisit.PreliminaryAuditUser.FullName : (qa.CurrentQCEnum == CurrentQC.Second ? qa.SubjectVisit.ReviewAuditUser.FullName : u.FullName),
QuestionId = qa.TrialQCQuestionConfigure.Id,
QuesitonName = qa.TrialQCQuestionConfigure.QuestionName,
ShowOrder = qa.TrialQCQuestionConfigure.ShowOrder,
Answer = qa.Answer,
};
var result = query.OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList();
var list = result.Where(t => t.AuditTime != null).GroupBy(t => new { t.SubjectVisitId, t.SecondReviewTime, t.TrialSiteCode, t.VisitNum, t.SubjectCode, t.VisitName, t.CurrentQCEnum })
.Select(g => new QCQuestionResult_Export()
{
TrialSiteCode = g.Key.TrialSiteCode,
SubjectCode = g.Key.SubjectCode,
VisitName = g.Key.VisitName,
CurrentQCEnum = g.Key.CurrentQCEnum,
VisitNum = g.Key.VisitNum,
AuditTime = g.Select(t => t.AuditTime).First(),
AuditUserName = g.Select(t => t.AuditUserName).First(),
AuditUserFullName = g.Select(t => t.AuditUserFullName).First(),
QuestionAnswerList = g.Select(t => new QCQuestionAnswerExport() { Answer = t.Answer, QuestionName = t.QuesitonName, ShowOrder = t.ShowOrder, QuestionId = t.QuestionId }).OrderBy(t => t.ShowOrder).ToList()
}).OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ThenBy(t => t.CurrentQCEnum).ThenBy(t => t.AuditTime).ToList();
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
var columNameList = _trialQCQuestionRepository.Where(t => t.TrialId == trialId).OrderBy(t => t.ShowOrder).Select(t => new { t.QuestionName, t.Id }).ToList();
var dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 2,
AutoColumnStartIndex = 7,
TempalteLastColumnIndex = 6,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "Answer",
DynamicItemTitleName = "QuestionName",
DynamicListName = "QuestionAnswerList",
DynamicItemTitleId = "QuestionId",
RemoveColunmIndexList = new List<int>() { },
ColumnIdNameList = columNameList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.Id, Name = t.QuestionName }).ToList(),
TranslateDicNameList = new List<string>()
};
var (memoryStream, fileName) = await ExcelExportHelper.DataExport_NpoiTestAsync(StaticData.Export.TrialQCResult_Export, exportInfo, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(QCQuestionResult_Export), dynamicColumnConfig: dynamicColumnConfig);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{exportInfo.ResearchProgramNo}_{exportInfo.CriterionName}_{fileName}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
}
/// <summary>
/// 医学审核问题导表
/// </summary>
/// <param name="inDto"></param>
/// <param name="_readingMedicineSystemQuestionRepository"></param>
/// <param name="_dictionaryService"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GetReadingMedicineSystemQuestionList_Export(ReadingMedicineSystemQuestionQuery inDto,
[FromServices] IRepository<ReadingMedicineSystemQuestion> _readingMedicineSystemQuestionRepository,
[FromServices] IDictionaryService _dictionaryService)
{
//避免前端遍历
var criterionEnum = inDto.TrialReadingCriterionId != null ? _readingQuestionCriterionTrialRepository.Where(t => t.Id == inDto.TrialReadingCriterionId).Select(t => t.CriterionType).FirstOrDefault() : CriterionType.NoCriterion;
var query = _readingMedicineSystemQuestionRepository.AsQueryable()
.WhereIf(!inDto.TypeValue.IsNullOrEmpty(), x => x.TypeValue.Contains(inDto.TypeValue))
.WhereIf(!inDto.ParentTriggerValue.IsNullOrEmpty(), x => x.ParentTriggerValue.Contains(inDto.ParentTriggerValue))
.WhereIf(!inDto.QuestionName.IsNullOrEmpty(), x => x.QuestionName.Contains(inDto.QuestionName))
.WhereIf(!inDto.Type.IsNullOrEmpty(), x => x.Type.Contains(inDto.Type))
.WhereIf(inDto.ReadingCategory != null, x => x.ReadingCategory == inDto.ReadingCategory)
.WhereIf(inDto.CurrentCriterionType != null, x => x.CriterionTypeEnum == null || x.CriterionTypeEnum == inDto.CurrentCriterionType)
.WhereIf(inDto.CriterionTypeEnum != null, x => x.CriterionTypeEnum == inDto.CriterionTypeEnum)
.WhereIf(inDto.TrialReadingCriterionId != null, x => x.CriterionTypeEnum == criterionEnum || x.IsGeneral == true)
.WhereIf(inDto.IsGeneral != null, x => x.IsGeneral == inDto.IsGeneral)
.WhereIf(inDto.LanguageType != null, x => x.LanguageType == inDto.LanguageType!.Value)
.ProjectTo<ReadingMedicineSystemQuestionView>(_mapper.ConfigurationProvider).OrderBy(x => x.ShowOrder);
var defalutSortArray = new string[] { nameof(ReadingMedicineSystemQuestionView.LanguageType) + " desc", nameof(ReadingMedicineSystemQuestionView.ShowOrder) };
var list = await query.SortToListAsync(inDto);
var exportInfo = new ExcelExportInfo();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
return await ExcelExportHelper.DataExportAsync(StaticData.Export.SystenMedicalQCQuestionsList_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(ReadingMedicineSystemQuestionView));
}
/// <summary>
/// 访视影像统计
/// </summary>
/// <param name="inQuery"></param>
/// <param name="_subjectVisitRepository"></param>
/// <param name="_trialRepository"></param>
/// <param name="_dictionaryService"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GetTrialVisitImageStatList_Export(TrialVisitImageQuery inQuery,
[FromServices] IRepository<SubjectVisit> _subjectVisitRepository,
[FromServices] IRepository<Trial> _trialRepository,
[FromServices] IDictionaryService _dictionaryService)
{
var query = _subjectVisitRepository.Where(t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectCode.IsNotNullOrEmpty(), t => t.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(inQuery.BeginScanDate != null, t => t.LatestScanDate >= inQuery.BeginScanDate)
.WhereIf(inQuery.EndScanDate != null, t => t.LatestScanDate == inQuery.EndScanDate)
.Select(t => new TrialVisitImageStatView()
{
TrialId = t.TrialId,
SubjectVisitId = t.Id,
SubjectCode = t.Subject.Code,
TrialSiteCode = t.TrialSite.TrialSiteCode,
TrialSiteId = t.TrialSiteId,
VisitName = t.VisitName,
VisitNum = t.VisitNum,
EarliestScanDate = t.EarliestScanDate,
LatestScanDate = t.LatestScanDate,
IsHaveDicom = t.StudyList.Any(),
IsHaveNoneDicom = t.NoneDicomStudyList.Any(),
TotalStudyCount = t.StudyList.Count() + t.NoneDicomStudyList.Count(),
TotalImageCount = t.StudyList.Sum(t => t.InstanceCount) + t.NoneDicomStudyList.Sum(t => t.FileCount),
TotalImageSize = t.StudyList.SelectMany(t => t.InstanceList).Sum(t => t.FileSize) + t.NoneDicomStudyList.SelectMany(t => t.NoneDicomFileList).Sum(t => t.FileSize),
});
var list = await query.Where(t => t.TotalImageCount > 0).SortToListAsync(inQuery);
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
return await ExcelExportHelper.DataExportAsync(StaticData.Export.TriaVisitImageStat_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(TrialMaintenanceDTO));
}
#region
@@ -563,6 +342,7 @@ namespace IRaCIS.Core.Application.Service.Common
}
#endregion
@@ -570,46 +350,6 @@ namespace IRaCIS.Core.Application.Service.Common
#region
/// <summary>
/// 重传申请导表
/// </summary>
/// <param name="inQuery"></param>
/// <param name="_subjectVisitImageBackRecordReposiotry"></param>
/// <param name="_dictionaryService"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GetImageBackList_Export(ImageBackQueryDto inQuery,
[FromServices] IRepository<SubjectVisitImageBackRecord> _subjectVisitImageBackRecordReposiotry, [FromServices] IDictionaryService _dictionaryService)
{
var svExpression = QCCommon.GetSubjectVisitImageBackRecordFilter(inQuery.VisitPlanArray);
var query = _subjectVisitImageBackRecordReposiotry.Where(t => t.SubjectVisit.TrialId == inQuery.TrialId)
.WhereIf(inQuery.TrialSiteId != null, t => t.SubjectVisit.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => t.SubjectVisit.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(!string.IsNullOrEmpty(inQuery.VisitName), t => t.SubjectVisit.VisitName.Contains(inQuery.VisitName))
.WhereIf(inQuery.VisitPlanArray != null && inQuery.VisitPlanArray?.Length > 0, svExpression)
.WhereIf(inQuery.CreateUserId != null, t => t.CreateUserId == inQuery.CreateUserId)
.WhereIf(inQuery.ApplyUserRole != null, t => t.ApplyUserRole == inQuery.ApplyUserRole)
.WhereIf(inQuery.ApplyBeginTime != null, t => t.CreateTime >= inQuery.ApplyBeginTime)
.WhereIf(inQuery.ApplyEndTime != null, t => t.CreateTime <= inQuery.ApplyEndTime)
.WhereIf(inQuery.ImageBackState != null, t => t.ImageBackState == inQuery.ImageBackState)
.WhereIf(inQuery.AuditBeginTime != null, t => t.AuditTime >= inQuery.AuditBeginTime)
.WhereIf(inQuery.AuditEndTime != null, t => t.AuditTime <= inQuery.AuditEndTime)
.ProjectTo<ImageBackViewModel>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(ImageBackViewModel.IsUrgent) + " desc", nameof(ImageBackViewModel.SubjectCode), nameof(ImageBackViewModel.VisitNum) };
var list = await query.OrderByDescending(t => t.IsUrgent).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToListAsync();
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialImageBackRecordList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(ImageBackViewModel));
}
///getSysDocumentConfirmList 系统文档培训查询
///
[HttpPost]
@@ -976,8 +716,6 @@ namespace IRaCIS.Core.Application.Service.Common
var svExpression = QCCommon.GetSubjectVisitFilter(inQuery.VisitPlanArray);
var query = _subjectVisitRepository.Where(x => x.TrialId == inQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.SubmitState >= SubmitStateEnum.ToSubmit)
.Where(t => t.Subject.FinalSubjectVisitId != null ? t.VisitNum <= t.Subject.FinalSubjectVisit.VisitNum : true)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectId != null, t => t.Subject.Id == inQuery.SubjectId)
@@ -1025,9 +763,6 @@ namespace IRaCIS.Core.Application.Service.Common
{
var svExpression = QCCommon.GetSubjectVisitFilter(inQuery.VisitPlanArray);
var query = _subjectVisitRepository.Where(x => x.TrialId == inQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.AuditState > AuditStateEnum.ToAudit)
.WhereIf(inQuery.VisitId != null, t => t.Id == inQuery.VisitId)
.WhereIf(inQuery.CurrentActionUserId != null, t => t.CurrentActionUserId == inQuery.CurrentActionUserId)
.WhereIf(inQuery.ChallengeState != null, t => t.ChallengeState == inQuery.ChallengeState)
@@ -1438,9 +1173,6 @@ namespace IRaCIS.Core.Application.Service.Common
var svExpression = QCCommon.GetDicomStudySubjectVisitFilter(studyQuery.VisitPlanArray);
var dicomStudyQuery = _dicomStudyRepository.Where(t => t.TrialId == studyQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.SubjectVisit.CheckState >= CheckStateEnum.CVIng)
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ClinicalResearchCoordinator || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA, t => t.Subject.TrialSite.CRCUserList.Any(t => t.UserId == _userInfo.UserRoleId))
.WhereIf(studyQuery.VisitPlanArray != null && studyQuery.VisitPlanArray?.Length > 0, svExpression)
.WhereIf(!string.IsNullOrWhiteSpace(studyQuery.SubjectInfo), t => t.Subject.Code.Contains(studyQuery.SubjectInfo))
@@ -1463,17 +1195,12 @@ namespace IRaCIS.Core.Application.Service.Common
//Uploader = t.Uploader.UserName,
//UploadTime = t.CreateTime
SubjectStatus = t.Subject.Status
});
var svExpression2 = QCCommon.GetNoneDicomStudySubjectVisitFilter(studyQuery.VisitPlanArray);
var nodeDicomStudyQuery = _noneDicomStudyRepository.Where(t => t.TrialId == studyQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.SubjectVisit.CheckState >= CheckStateEnum.CVIng)
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ClinicalResearchCoordinator || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA, t => t.Subject.TrialSite.CRCUserList.Any(t => t.UserId == _userInfo.UserRoleId))
.WhereIf(studyQuery.VisitPlanArray != null && studyQuery.VisitPlanArray?.Length > 0, svExpression2)
.WhereIf(!string.IsNullOrWhiteSpace(studyQuery.SubjectInfo), t => t.Subject.Code.Contains(studyQuery.SubjectInfo))
@@ -1497,7 +1224,7 @@ namespace IRaCIS.Core.Application.Service.Common
//Uploader = t.CreateUser.UserName,
//UploadTime = t.CreateTime
SubjectStatus = t.Subject.Status
});
@@ -1537,9 +1264,6 @@ namespace IRaCIS.Core.Application.Service.Common
var svExpression = QCCommon.GetSubjectVisitFilter(inQuery.VisitPlanArray);
var query = _subjectVisitRepository.Where(x => x.TrialId == inQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.CheckState >= CheckStateEnum.CVIng)
.Where(x => x.AuditState == AuditStateEnum.QCPassed) //一致性核查中的,或者还没一致性核查的
.WhereIf(inQuery.CheckState != null, t => t.CheckState == inQuery.CheckState)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
@@ -1578,9 +1302,7 @@ namespace IRaCIS.Core.Application.Service.Common
[FromServices] IRepository<Trial> _trialRepository)
{
var list = await _visitTaskRepository.Where(t => t.TrialId == inQuery.TrialId && t.IsAnalysisCreate == false)
//.Where(t => t.IsAnalysisCreate == false && t.DoctorUserId != null)
.Where(t => t.Subject.IsSubjectQuit == false || t.ReadingTaskState >= ReadingTaskState.Reading)
//.Where(t => t.IsAnalysisCreate == false && t.DoctorUserId != null)
.WhereIf(inQuery.IsEffect == true, t => t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze)
.WhereIf(inQuery.IsEffect == false, t => t.TaskState == TaskState.Adbandon || t.TaskState == TaskState.HaveReturned)
@@ -1964,7 +1686,7 @@ namespace IRaCIS.Core.Application.Service.Common
//.WhereIf(inQuery.SystemLevel == null, t => t.SystemLevel == SysEmailLevel.not_sys)
.WhereIf(inQuery.SystemLevel != null, t => t.SystemLevel == inQuery.SystemLevel)
.WhereIf(inQuery.IsDistinguishCriteria != null, t => t.IsDistinguishCriteria == inQuery.IsDistinguishCriteria)
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeList.Any(c => c == inQuery.CriterionTypeEnum))
.WhereIf(inQuery.CriterionTypeEnum != null, t => t.CriterionTypeEnum == inQuery.CriterionTypeEnum)
.WhereIf(inQuery.BusinessScenarioEnum != null, t => t.BusinessScenarioEnum == inQuery.BusinessScenarioEnum)
.WhereIf(inQuery.IsReturnRequired != null, t => t.IsReturnRequired == inQuery.IsReturnRequired)
.WhereIf(inQuery.IsEnable != null, t => t.IsEnable == inQuery.IsEnable)
@@ -1981,7 +1703,7 @@ namespace IRaCIS.Core.Application.Service.Common
return await ExcelExportHelper.DataExportAsync(StaticData.Export.EmailNoticeConfig_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(EmailNoticeConfigExportDto));
}
#endregion
#region
@@ -2105,7 +1827,7 @@ namespace IRaCIS.Core.Application.Service.Common
{
//两个人都做了
if (resultExceptJudgeList.Where(t => t.VisitTaskNum == item.VisitTaskNum && t.SubjectCode == item.SubjectCode).Select(t => t.ArmEnum).Distinct().Count() == 2)
if (resultExceptJudgeList.Count(t => t.VisitTaskNum == item.VisitTaskNum && t.SubjectCode == item.SubjectCode) == 2)
{
//如果没有产生裁判,默认选择R1
if (item.ArmEnum == Arm.DoubleReadingArm1)
@@ -2160,7 +1882,6 @@ namespace IRaCIS.Core.Application.Service.Common
//已完成的全局数量
finishedGlobalCount = resultExceptJudgeList.Where(t => t.SubjectCode == visitItem.SubjectCode && subjectReadingPeriondVisitNumList.Any(c => (c + addReadingPeriodNum) == t.VisitTaskNum)
/*&& t.ReadingCategory == ReadingCategory.Global*/)
.Select(t => new { t.VisitTaskNum, t.ArmEnum }).Distinct()
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).Count();
}
@@ -2174,8 +1895,7 @@ namespace IRaCIS.Core.Application.Service.Common
var subjectMaxFinishedGlobalTaskNum = resultExceptJudgeList.Where(t => t.SubjectCode == visitItem.SubjectCode && subjectReadingPeriondVisitNumList.Any(c => (c + addReadingPeriodNum) == t.VisitTaskNum)
/*&& t.ReadingCategory == ReadingCategory.Global*/)
.Select(t => new { t.VisitTaskNum, t.ArmEnum }).Distinct()
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).DefaultIfEmpty().Max();
.GroupBy(t => t.VisitTaskNum).Where(g => g.Count() == 2).Select(g => g.Key).DefaultIfEmpty().Max();
//最大的完成的全局是否产生裁判
if (subjectJudgeList.Any(t => t.VisitTaskNum == (subjectMaxFinishedGlobalTaskNum + ReadingCommon.TaskNumDic[ReadingCategory.Judge])))
@@ -2307,7 +2027,7 @@ namespace IRaCIS.Core.Application.Service.Common
#endregion
#endregion
#region
@@ -2429,24 +2149,6 @@ namespace IRaCIS.Core.Application.Service.Common
else if (criterion.CriterionType == CriterionType.PCWG3)
{
}
else if (criterion.CriterionType == CriterionType.SelfDefine)
{
//自定义的又问题名称重复 所以统一加上组名
//有重复的就加,没有重复的就不加
if (list.Any(t => t.QuestionAnswerList.Select(t => t.QuestionName).Count() != t.QuestionAnswerList.Select(t => t.QuestionName).Distinct().Count()))
{
foreach (var item in list)
{
foreach (var qs in item.QuestionAnswerList)
{
qs.QuestionName = qs.Group + "_" + qs.QuestionName;
}
}
}
}
#endregion
@@ -2597,19 +2299,13 @@ namespace IRaCIS.Core.Application.Service.Common
list.Add(new ExportDocumentDes() { Code = StaticData.Export.ReadingLession_Export, ExportCatogory = ExportResult.DetailedTableOfLesions });
}
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.TumorCDISC_Export, ExportCatogory = ExportResult.TumorCDISC_Export });
}
if (criterion.CriterionType == CriterionType.OCT)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.OCT_ReadingLession_Export, ExportCatogory = ExportResult.OCT_ReadingLession_Export });
}
if (criterion.CriterionGroup != CriterionGroup.Tumor)
//else if (criterion.CriterionType == CriterionType.SelfDefine)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.CDISC_Reading_Export, ExportCatogory = ExportResult.NoneTumorCDISC });
list.Add(new ExportDocumentDes() { Code = StaticData.Export.CDISC_Reading_Export, ExportCatogory = ExportResult.CDISC });
}
switch (criterion.ArbitrationRule)
@@ -2790,51 +2486,34 @@ namespace IRaCIS.Core.Application.Service.Common
}
// 非肿瘤标准 包括自定义,以及系统非肿瘤 会配置到表格层级 问题名称是 表格名称_表格子问题名
else if (
criterion.CriterionGroup == CriterionGroup.Nontumorous && inQuery.ReadingExportType != ExportResult.NoneTumorCDISC
criterion.CriterionGroup == CriterionGroup.Nontumorous && inQuery.ReadingExportType != ExportResult.CDISC
)
{
// 配置在外层问题 或者表格问题上
taskList = await query.ProjectTo<CommonLessionExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
// CDISC 导出 只管到 外层问题层级 和阅片结果表是保持一致
else if (inQuery.ReadingExportType == ExportResult.NoneTumorCDISC)
else if (inQuery.ReadingExportType == ExportResult.CDISC)
{
if (criterion.CriterionGroup == CriterionGroup.Tumor)
{
list = await query.ProjectTo<CommonEvaluationExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
else
{
taskList = await query.ProjectTo<CommonLessionExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
list = await query.ProjectTo<CommonEvaluationExport>(_mapper.ConfigurationProvider,
new
{
readingExportType = inQuery.ReadingExportType,
criterionType = criterion.CriterionType,
arbitrationRule = criterion.ArbitrationRule,
trialReadingCriterionId = inQuery.TrialReadingCriterionId,
isEn_Us = _userInfo.IsEn_Us
}).ToListAsync();
}
@@ -2875,7 +2554,7 @@ namespace IRaCIS.Core.Application.Service.Common
#endregion
if (inQuery.ReadingExportType != ExportResult.NoneTumorCDISC)
if (inQuery.ReadingExportType != ExportResult.CDISC)
{
//最终EXCEL 列
var configCoumNameList = new List<DynamicColumnConfig.ColumItem>();
@@ -2904,8 +2583,7 @@ namespace IRaCIS.Core.Application.Service.Common
{
#region
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC)
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB || criterion.CriterionType == CriterionType.IRECIST1Point1)
{
fistLeveLNameList = trialConfigQuestionList.Select(t => new DynamicColumnConfig.ColumItem()
@@ -2948,8 +2626,7 @@ namespace IRaCIS.Core.Application.Service.Common
var extralNameList = new List<DynamicColumnConfig.ColumItem>();
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC
|| criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
{
//if(inQuery.ReadingExportType == ExportResult.DetailedTableOfLesions)
@@ -2990,8 +2667,7 @@ namespace IRaCIS.Core.Application.Service.Common
var addLessionInfoList = new List<CommonQuesionInfo>();
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC
|| criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
{
//病灶编号 和病灶类型没有配置,但是需要有的
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionName = _userInfo.IsEn_Us ? "Lesion ID" : "病灶编号", QuestionValue = lession.LessionCode });
@@ -3031,8 +2707,7 @@ namespace IRaCIS.Core.Application.Service.Common
#endregion
#region list taskList list
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC)
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB || criterion.CriterionType == CriterionType.IRECIST1Point1)
{
//针对1.1 整体肿瘤评估 有的两列要合并一列
foreach (var item in list)
@@ -3111,7 +2786,7 @@ namespace IRaCIS.Core.Application.Service.Common
//多表格,增加的一列,就用Guid.Empty 标识
var extralNameList = new List<DynamicColumnConfig.ColumItem>() { new DynamicColumnConfig.ColumItem() { Id = Guid.Empty, Name = _userInfo.IsEn_Us ? "Table Name" : "表格名称" } };
var tableQuestionNameList = trialConfigTableQuestionList.OrderBy(t => t.TableName).Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.TableName + "_" + t.QuestionName }).ToList();
var tableQuestionNameList = trialConfigTableQuestionList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.TableName + "_" + t.QuestionName }).ToList();
configCoumNameList = fistLeveLNameList.Union(extralNameList).Union(tableQuestionNameList).ToList();
}
@@ -3201,236 +2876,42 @@ namespace IRaCIS.Core.Application.Service.Common
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
//CDISCList = new List<string>(),
CDISCList = new List<string>(),
TranslateDicNameList = translateDicNameList
};
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export)
{
dynamicColumnConfig.TempalteLastColumnIndex = 8;
}
}
else
{
//CDISC 导出 只到外层问题级别 使用Id 填充Excel
if (criterion.CriterionGroup == CriterionGroup.Nontumorous)
var totalConfigCoumNameList = trialConfigQuestionList.ToList();
var configCoumNameList = totalConfigCoumNameList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName }).ToList();
var translateDicList = totalConfigCoumNameList.Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
var cdiscCodeList = totalConfigCoumNameList.Select(t => t.CDISCCode).ToList();
dynamicColumnConfig = new DynamicColumnConfig()
{
//最终EXCEL 列
var configCoumNameList = new List<DynamicColumnConfig.ColumItem>();
//表格全问题名
var trialConfigTableQuestionList = _trialReadingTableQuestionRepository.Where(t => t.TrialId == trialId && t.TrialCriterionId == trialReadingCriterionId).Where(t => t.ExportResultStr.Contains(((int)inQuery.ReadingExportType).ToString()))
.Select(t => new ExportQuestionBasicInfo()
{
QuestionId = t.Id,
TableName = _userInfo.IsEn_Us ? t.ReadingQuestionTrial.QuestionEnName : t.ReadingQuestionTrial.QuestionName,
QuestionName = _userInfo.IsEn_Us ? t.QuestionEnName : t.QuestionName,
CDISCCode = t.CDISCCode,
TranslateDicName = t.DictionaryCode,
ShowOrder = t.ShowOrder
}).OrderBy(t => t.TableName).ThenBy(t => t.ShowOrder).ToList();
var isMutiTable = trialConfigTableQuestionList.Select(t => t.TableName).Distinct().Count() > 1;
//外层问题处理
var fistLeveLNameList = trialConfigQuestionList.Select(t => new DynamicColumnConfig.ColumItem()
{
Id = t.QuestionId,
Name = t.QuestionName,
CDISCCode = t.CDISCCode
}).ToList();
var translateDicNameList = trialConfigQuestionList.Union(trialConfigTableQuestionList).Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
//单表格问题(直接用表格问题名称) 或者没有表格问题
if (isMutiTable == false)
{
//一个表格,或者没有表格
var tableNameList = trialConfigTableQuestionList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName, CDISCCode = t.CDISCCode }).ToList();
configCoumNameList = fistLeveLNameList.Union(tableNameList).ToList();
//最终的病灶列表 要把裁判的也要加进去,需要处理裁判标记
foreach (var item in taskList.OrderBy(t => t.SubjectCode).ThenBy(t => t.ArmEnum).ThenBy(t => t.VisitTaskNum))
{
//有病灶,并且至少配置了一个病灶问题
if (item.LesionList.Count > 0 && item.LesionList.SelectMany(t => t.LessionAnswerList).Any())
{
foreach (var lession in item.LesionList.OrderBy(t => t.LessionType).ThenBy(t => t.LessionCode))
{
//病灶配置了问题 才进行导出
if (lession.LessionAnswerList.Count > 0)
{
var dynamicLessionInfoList = new List<CommonQuesionInfo>();
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
var cloneItem = item.Clone();
cloneItem.QuestionAnswerList = dynamicLessionInfoList;
list.Add(cloneItem);
}
}
}
else
{
//要把裁判任务加进去 裁判任务上没有病灶
list.Add(item);
}
}
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicNameList
};
}
else
{
//多表格问题,需要用新的模板,并且用行的方式展示 (不是之前表格名称_表格子问题名的方式)
export_Template = StaticData.Export.CDISC_MutiTable_Reading_Export;
configCoumNameList = fistLeveLNameList.ToList();
var translateUnitList = await _dictionaryService.GetBasicDataSelect("ValueUnit");
foreach (var item in taskList.OrderBy(t => t.SubjectCode).ThenBy(t => t.ArmEnum).ThenBy(t => t.VisitTaskNum))
{
//有病灶,并且至少配置了一个病灶问题
if (item.LesionList.Count > 0 && item.LesionList.SelectMany(t => t.LessionAnswerList).Any())
{
foreach (var lession in item.LesionList.OrderBy(t => t.LessionType).ThenBy(t => t.LessionCode))
{
//病灶配置了问题 才进行导出
if (lession.LessionAnswerList.Count > 0)
{
//遍历病灶的每个问题
foreach (var lessionItem in lession.LessionAnswerList.OrderBy(t => t.ShowOrder))
{
var cloneItem = item.Clone();
string unitText = lessionItem.Unit switch
{
ValueUnit.Custom => lessionItem.CustomUnit,
null => "",
_ => _userInfo.IsEn_Us ? translateUnitList.FirstOrDefault(t => t.Code == ((int)lessionItem.Unit).ToString())?.Value ?? ""
: translateUnitList.FirstOrDefault(t => t.Code == ((int)lessionItem.Unit).ToString())?.ValueCN ?? ""
};
cloneItem.Group = lessionItem.TableName;
cloneItem.TRLINKID = lessionItem.RowId.ToString();
cloneItem.TRORRES = lessionItem.QuestionValue;
cloneItem.TRTEST = lessionItem.CDISCCode;
cloneItem.TRORRESU = unitText;
list.Add(cloneItem);
}
//var dynamicLessionInfoList = new List<CommonQuesionInfo>();
//var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
////两部分组成 外层问题+ 动态的表格问题
//dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
}
}
}
else
{
//要把裁判任务加进去 裁判任务上没有病灶
list.Add(item);
}
}
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 15,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicNameList
};
}
}
else
{
//CDISC 导出 只到外层问题级别 使用Id 填充Excel
var totalConfigCoumNameList = trialConfigQuestionList.ToList();
var configCoumNameList = totalConfigCoumNameList.Select(t => new DynamicColumnConfig.ColumItem() { Id = t.QuestionId, Name = t.QuestionName, CDISCCode = t.CDISCCode }).ToList();
var translateDicList = totalConfigCoumNameList.Where(t => t.TranslateDicName.IsNotNullOrEmpty()).Select(t => t.TranslateDicName).Distinct().ToList();
var cdiscCodeList = totalConfigCoumNameList.Select(t => t.CDISCCode).ToList();
dynamicColumnConfig = new DynamicColumnConfig()
{
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
IsCDISCExport = true,
//CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicList
};
}
AutoColumnTitleRowIndex = 1,
AutoColumnStartIndex = 6,
TempalteLastColumnIndex = 10,
DynamicItemDicName = "TranslateDicName",
DynamicItemValueName = "QuestionValue",
DynamicItemTitleName = "QuestionName",
DynamicItemTitleId = "QuestionId",
DynamicListName = "QuestionAnswerList",
RemoveColunmIndexList = removeColumnIndexList,
ColumnIdNameList = configCoumNameList,
CDISCList = cdiscCodeList,
TranslateDicNameList = translateDicList
};
@@ -3481,7 +2962,7 @@ namespace IRaCIS.Core.Application.Service.Common
string fileName = "";
if (inQuery.ReadingExportType == ExportResult.NoneTumorCDISC)
if (inQuery.ReadingExportType == ExportResult.CDISC)
{
(memoryStream, fileName) = await ExcelExportHelper.DataExport_NpoiTestAsync(export_Template, exportInfo, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(CommonEvaluationExport), criterion.CriterionType, dynamicColumnConfig);
@@ -3813,7 +3294,5 @@ namespace IRaCIS.Core.Application.Service.Common
}
#endregion
}
}
@@ -1,514 +0,0 @@
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.Common;
public class TumorCommonQustionInfo
{
//问题标识,肿瘤评估用于区分是什么问题
public QuestionType? QuestionType { get; set; }
public Guid QuestionId { get; set; }
public string QuestionName { get; set; }
public string QuestionValue { get; set; }
public string TranslateDicName { get; set; }
public ValueUnit? Unit { get; set; }
public ValueOfType? ValueType { get; set; }
}
public class TumorLessionInfo
{
public Guid Id { get; set; }
public Guid? OrganInfoId { get; set; }
//病灶编号
public string LessionCode { get; set; }
public LesionType? LessionType { get; set; }
public Guid? SplitRowId { get; set; }
public string? DicomModality { get; set; }
public string? NoneDicomModality { get; set; }
//病灶答案
public List<TumorLessionAnswerInfo> LessionAnswerList { get; set; }
}
public class TumorLessionAnswerInfo
{
public QuestionMark? QuestionMark { get; set; }
//病灶Id
public Guid RowId { get; set; }
//如果是4 就取CustomUnit 否则就是字典翻译
[Comment("单位")]
public ValueUnit? Unit { get; set; }
public int ShowOrder { get; set; }
public Guid TableQuesionId { get; set; }
public string QuestionName { get; set; }
public string QuestionValue { get; set; }
public string TranslateDicName { get; set; }
}
public class TumorGlobalQuestionAnserInfo
{
[Comment("原任务ID")]
public Guid TaskId { get; set; }
public GlobalAnswerType GlobalAnswerType { get; set; }
[Comment("问题答案")]
public string Answer { get; set; } = string.Empty;
}
public class TumorExportBaseModel : TU_TR_RSBaseModel
{
public List<TumorLessionInfo> LesionList { get; set; } = new List<TumorLessionInfo>();
public List<TumorCommonQustionInfo> QuestionAnswerList { get; set; }
public List<TumorGlobalQuestionAnserInfo> GlobalResultList { get; set; }
#region
public DateTime? JudgeSignTime { get; set; }
public Guid? SourceSubjectVisitId { get; set; }
public List<decimal> SubjectCriterionReadingPeriodVisitNumList { get; set; }
public decimal VisitTaskNum { get; set; }
public ReadingTaskState ReadingTaskState { get; set; }
public ReadingCategory ReadingCategory { get; set; }
//裁判结果选择的访视或者全局任务Id
public Arm? JudgeArmEnum { get; set; }
//在当前访视触发裁判,或者在截止日期小于等于当前访视的阅片期触发裁判
[DictionaryTranslateAttribute("YesOrNoAudit")]
public bool? IsTrigerJudge { get; set; }
//(如果是访视点裁判,则仅在所选阅片人对应访视 显示;如果是阅片期裁判,则在所选阅片人 阅片期内的所有访视 显示此原因)
public string JudgeNote { get; set; } = string.Empty;
public string VisitNote { get; set; }
#endregion
}
public class TU_TR_RSBaseModel
{
/// <summary>
/// 方案编号 STUDYID
/// </summary>
public string ResearchProgramNo { get; set; }
/// <summary>
/// 域 DOMAIN TU TR RS
/// </summary>
public string Domain { get; set; }
/// <summary>
/// 取值类型 TUSPID TRSPID RSSPID
/// </summary>
public string ValueType { get; set; }
/// <summary>
/// 受试者编号 USUBJID 实际展示TrialSiteSubjectCode
/// </summary>
public string SubjectCode { get; set; }
/// <summary>
/// 供应商 TUNAM (Extensive Imaging)
/// </summary>
public string Vendor { get; set; } = "Extensive Imaging";
/// <summary>
/// 阅片人 TUEVAL TREVAL RSEVAL
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 阅片人标识 TUEVALID TREVALID RSEVALID
/// </summary>
public Arm ArmEnum { get; set; }
/// <summary>
/// 访视编号 VISITNUM
/// </summary>
public decimal? VisitNum { get; set; }
/// <summary>
/// 访视名称 VISIT
/// </summary>
public string? VisitName { get; set; }
/// <summary>
/// 拍片日期 TUDTC TRDTC RSDTC
/// </summary>
public DateTime? LatestScanDate { get; set; }
public DateTime? EarliestScanDate { get; set; }
/// <summary>
/// eCRF标识 TUREFID TRREFID RSREFID
/// </summary>
public Guid VisitTaskId { get; set; }
#region
/// <summary>
/// RSCAT 阅片标准
/// </summary>
public string CriterionName { get; set; }
/// <summary>
/// RSACPTFL 裁定标记 TUACPTFL
/// </summary>
//裁判选择标记
//根据裁判的任务结果 设置访视任务的这个字段 该字段表示 裁判认同该任务的结果
public bool? IsJudgeSelect { get; set; }
#endregion
public DateTime? SignTime { get; set; }
public string TaskName { get; set; }
#region
public string IsJudgeSelectStr => IsJudgeSelect == true ? "Y" : "";
public bool IsTargetPD { get; set; } = false;
public bool IsOverallResponsePD { get; set; } = false;
//TR表 靶病灶PD了,访视层级的都是最早拍片日期 RS表 整体肿瘤评估PD了,那么疗效评估的拍片日期都给最早的
public bool IsPD => Domain == "TR" ? IsTargetPD : IsOverallResponsePD;
public string ArmEnumStr { get; set; }
public string LatestScanDateStr
{
get
{
var date = IsPD ? EarliestScanDate : LatestScanDate;
return date?.ToString("yyyy-MM-dd") ?? "";
}
}
public string TrialSiteSubjectCode => ResearchProgramNo + SubjectCode;
#endregion
}
public class TU_Export : TU_TR_RSBaseModel
{
///// <summary>
///// 取值类型 TUSPID
///// </summary>
//public string TUValueType { get; set; }
/// <summary>
/// 序号 TUSEQ (同一个访视,所有阅片人选择病灶给个顺序号)
/// </summary>
public int No { get; set; }
/// <summary>
/// 链接ID TULNKID (阅片人角色_病灶编号)不同访视可以重复
/// </summary>
public string ARM_TumorNo { get; set; }
/// <summary>
/// 肿瘤识别简称 TUTESTCD
/// </summary>
public string TumorIdentificationSimple { get; set; }
/// <summary>
/// 肿瘤识别全称 TUTEST
/// </summary>
public string TumorIdentificationFullName { get; set; }
/// <summary>
/// 肿瘤鉴定结果 TUORRES
/// </summary>
public string TumorIdentificationResult { get; set; }
/// <summary>
/// 肿瘤识别结果类型 TUSTRESC
/// </summary>
public string TumorIdentificationResultType { get; set; }
/// <summary>
/// 部位 TULOC (对应病灶表的部位,需要国际化)
/// </summary>
public string BodyPart { get; set; }
/// <summary>
/// 鉴定方法 TUMETHOD (Modality?)
/// </summary>
public string IdentificationMethod { get; set; }
///// <summary>
///// 裁定标记 TUACPTFL
///// </summary>
//public bool? IsJudgeSelect { get; set; }
/// <summary>
/// 部位描述 LOCTEXT
/// </summary>
public string BodyPartDes { get; set; }
}
public class TR_Export : TU_TR_RSBaseModel
{
///// <summary>
///// 取值类型 TRSPID
///// </summary>
//public string TRValueType { get; set; }
/// <summary>
/// 每个subject 按照顺序编号 TRSEQ
/// </summary>
public int TRSEQ { get; set; }
/// <summary>
///TRGRPID 组ID 对应TU表肿瘤鉴定结果 TumorIdentificationResult
/// </summary>
public string TRGRPID { get; set; }
/// <summary>
/// TRLNKID 链接ID 对应TU表的链接ID TumorNo(阅片人角色_病灶编号)
/// </summary>
public string ARM_TumorNo { get; set; }
/// <summary>
///TRLNKGRP 链接组 ARM-任务名(访视名) 对应RS的链接组
/// </summary>
public string ARM_VisitName { get; set; }
/// <summary>
/// 肿瘤评估简称 TRTESTCD
/// </summary>
public string TumorAssessmentSimpleName { get; set; }
/// <summary>
/// 肿瘤评估全称 TRTEST
/// </summary>
public string TumorAssessmentFullName { get; set; }
/// <summary>
/// 原始测量 TRORRES
/// </summary>
public string OriginalMeasurements { get; set; }
/// <summary>
/// 原始单位 TRORRESU
/// </summary>
public string OriginalUnit { get; set; }
/// <summary>
/// 标准结果(字符) TRSTRESC
/// </summary>
public string StandardResult_Character => OriginalMeasurements;
/// <summary>
/// 标准结果(数值) TRORRESU
/// </summary>
public string StandardResult_Numeric => double.TryParse(OriginalMeasurements, out _) ||
(OriginalMeasurements?.EndsWith("%") == true &&
double.TryParse(OriginalMeasurements.TrimEnd('%'), out _))
? OriginalMeasurements
: "";
/// <summary>
/// 标准单位 TRSTRESU
/// </summary>
public string StandardUnit => OriginalUnit;
/// <summary>
/// 完成状态 TRSTAT
/// </summary>
public string CompletionStatus { get; set; }
/// <summary>
/// 完成状态 TRMETHOD
/// </summary>
public string IdentificationMethod { get; set; }
/// <summary>
/// 无法测量原因 TRREASND
/// </summary>
public string NotMeasuredReason { get; set; }
}
public class RS_Export : TU_TR_RSBaseModel
{
/// <summary>
/// RSSEQ 按照subject 的数据顺序编号
/// </summary>
public int RSSEQ { get; set; }
/// <summary>
/// RSLNKGRP 链接组 ARM_任务名(访视名)
/// </summary>
public string ARM_VisitName { get; set; }
/// <summary>
/// RSTESTCD 疗效评估简称
/// </summary>
public string EfficacyEvaluationSimpleName { get; set; }
/// <summary>
/// RSTEST 疗效评估全称
/// </summary>
public string EfficacyEvaluationName { get; set; }
/// <summary>
/// RSORRES 响应评估原始结果
/// </summary>
public string RespondEfficacyAssessment { get; set; }
/// <summary>
/// RSSTRESC 标准疗效评估
/// </summary>
public string StandardEfficacyAssessment => RespondEfficacyAssessment;
/// <summary>
/// RSSTAT 完成状态
/// </summary>
public string CompletionStatus { get; set; }
/// <summary>
/// RSREASND 无法评估原因
/// </summary>
public string NotAssessmentReason { get; set; }
///// <summary>
///// 裁定标记 RSACPTFL
///// </summary>
//public bool? IsJudgeSelect { get; set; }
/// <summary>
/// REASASM 评估原因
/// </summary>
public string AssessmentReason { get; set; }
/// <summary>
/// REASOVR 重新评估原因
/// </summary>
public string ReAssessmentReason { get; set; }
/// <summary>
/// REASUPD 更新评估原因
/// </summary>
public string UpdateAssessmentReason { get; set; }
[JsonIgnore]
public bool? IsOveralResponse { get; set; }
}
public class CO_Export : TU_TR_RSBaseModel
{
/// <summary>
/// 关联域 RS:(访视点备注) 空:裁判选择原因
/// </summary>
public string RDOMAIN { get; set; }
/// <summary>
/// COSEQ 序号
/// </summary>
public int COSEQ { get; set; }
/// <summary>
/// IDVAR 标识变量 RSSEQ 空:裁判选择原因
/// </summary>
public string IdentificationVariable { get; set; }
/// <summary>
/// 标识 IDVARVAL RSSEQ具体的值 空:裁判选择原因
/// </summary>
public string Identification { get; set; }
/// <summary>
/// COREF 备注引用
/// </summary>
public string RemarksQuote { get; set; }
/// <summary>
/// 备注 COVAL
/// </summary>
public string Remarks { get; set; }
/// <summary>
/// 裁决日期 CODTC
/// </summary>
public string CODTC { get; set; }
}
File diff suppressed because it is too large Load Diff
@@ -31,7 +31,5 @@ namespace IRaCIS.Application.Interfaces
Task<Dictionary<string, List<BasicDicSelectCopy>>> GetBasicDataSelect(string[] searchArray);
Task<List<BasicDicSelect>> GetBasicDataSelect(string searchKey);
}
}
@@ -1,23 +0,0 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:47Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces;
public interface IEmailLogService
{
Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inQuery);
Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog);
Task<IResponseOutput> DeleteEmailLog(Guid emailLogId);
}
@@ -5,51 +5,14 @@ using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using Medallion.Threading;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MimeKit;
using System.Net.Mail;
using System.Runtime.CompilerServices;
namespace IRaCIS.Core.Application.Service
{
public static class SafeMailHelper
{
public static async Task Run(Func<Task> func, [CallerMemberName] string caller = "")
{
try
{
await func();
}
catch (Exception ex)
{
Log.Logger.Error($"【邮件失败 - {caller}】: {ex.Message}");
}
}
public static async Task<T?> Run<T>(Func<Task<T>> func, [CallerMemberName] string caller = "")
{
try
{
return await func();
}
catch (Exception ex)
{
Log.Logger.Error($"【邮件失败 - {caller}】: {ex.Message}");
}
return default;
}
}
public interface IMailVerificationService
{
@@ -75,14 +38,8 @@ namespace IRaCIS.Core.Application.Service
Task<(Guid identityUserId, Guid userRoleId)> DoctorJoinTrialEmail(Guid trialId, Guid doctorId, string baseUrl, string rootUrl);
Task UserFeedBackMail(Guid feedBackId);
Task AfterUserModifyPasswordSendEmailAsync(Guid userId);
Task SiteSuervyCheckUser(Guid trialId, string email, string name);
Task SiteSuervyUpdateUser(Guid trialSiteId, string email, string name, string url);
}
[ApiExplorerSettings(GroupName = "Common")]
public class MailVerificationService(IRepository<VerificationCode> _verificationCodeRepository,
IRepository<SystemBasicData> _systemBasicDatarepository,
IRepository<VisitTask> _visitTaskRepository,
@@ -97,14 +54,12 @@ namespace IRaCIS.Core.Application.Service
IRepository<UserType> _userTypeRepository,
IRepository<Doctor> _doctorTypeRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IDistributedLockProvider _distributedLockProvider, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IMailVerificationService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
private async Task<EmailNoticeConfig> GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario scenario, MimeMessage messageToSend,
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
@@ -113,8 +68,6 @@ namespace IRaCIS.Core.Application.Service
if (configInfo == null)
{
Log.Logger.Error($"系统未找到当前场景:{scenario}邮件配置信息");
throw new BusinessValidationFailedException("系统未找到当前场景邮件配置信息,请联系运维人员核查");
}
@@ -128,7 +81,6 @@ namespace IRaCIS.Core.Application.Service
}
catch (Exception ex)
{
Log.Logger.Error($"邮件模板内容有误,填充内容出现问题,需要核查{scenario}场景邮件配置信息");
throw new BusinessValidationFailedException("邮件模板内容有误,填充内容出现问题,请联系运维人员核查");
}
@@ -151,8 +103,6 @@ namespace IRaCIS.Core.Application.Service
if (configInfo == null)
{
Log.Logger.Error($"系统未找到当前场景:{scenario}邮件配置信息");
throw new BusinessValidationFailedException("系统未找到当前场景邮件配置信息,请联系运维人员核查");
}
@@ -275,10 +225,9 @@ namespace IRaCIS.Core.Application.Service
}
//中心调研 登陆 发送验证码
//中心调研 登陆
public async Task AnolymousSendEmail(string researchProgramNo, string emailAddress, int verificationCode)
{
//throw new BusinessValidationFailedException("模拟邮件取数据或者发送异常!!!");
var messageToSend = new MimeMessage();
//发件地址
@@ -444,6 +393,7 @@ namespace IRaCIS.Core.Application.Service
}
//不登录 通过邮箱重置密码
public async Task AnolymousSendEmailForResetAccount(string emailAddress, int verificationCode)
{
@@ -533,7 +483,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
}
@@ -593,7 +543,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -723,7 +673,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.DoctorUserFirstJoinTrial : EmailBusinessScenario.DoctorUserExistJoinTrial, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
//创建账号 和创建角色 一条,更新的时候才记录更新角色
@@ -773,7 +723,7 @@ namespace IRaCIS.Core.Application.Service
if (feedBack.VisitTaskId != null)
{
var emailType = await _dictionaryRepository.Where(t => t.Parent.Code == "FeedBackTypeToIR" && t.ParentId != null && t.Code == ((int)feedBack.QuestionType).ToString()).Select(t => _userInfo.IsEn_Us ? t.Value : t.ValueCN).FirstOrDefaultAsync();
var emailType = await _dictionaryRepository.Where(t => t.Parent.Code == "Email_BusinessScenario" && t.ParentId != null && t.Code == ((int)EmailBusinessScenario.IRImageError).ToString()).Select(t => _userInfo.IsEn_Us ? t.Value : t.ValueCN).FirstOrDefaultAsync();
var info = await _visitTaskRepository.Where(t => t.Id == feedBack.VisitTaskId).Select(t => new { t.Trial.ResearchProgramNo, t.Trial.TrialCode, SubejctCode = t.Subject.Code, t.SourceSubjectVisit.VisitName }).FirstNotNullAsync();
@@ -887,112 +837,5 @@ namespace IRaCIS.Core.Application.Service
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
//用户修改密码发送邮件
public async Task AfterUserModifyPasswordSendEmailAsync(Guid userId)
{
var sysUserInfo = (await _identityUserRepository.Where(t => t.Id == userId).FirstOrDefaultAsync()).IfNullThrowException();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(sysUserInfo.FullName, sysUserInfo.EMail));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
sysUserInfo.FullName
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.IdentityUser_ModifyPassword, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
//中心调研核对人员提醒
public async Task SiteSuervyCheckUser(Guid trialId, string email, string name)
{
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.TrialCode, t.EmailFromName, t.ResearchProgramNo }).FirstOrDefaultAsync();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(name, email));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.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(ReplaceCompanyName(input.htmlBodyStr),
name,
trialInfo.TrialCode,
trialInfo.ResearchProgramNo
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_CheckUser, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
public async Task SiteSuervyUpdateUser(Guid trialSiteId, string email, string name, string url)
{
var trialInfo = await _trialSiteRepository.Where(t => t.Id == trialSiteId).Select(t => new { t.Trial.TrialCode, t.Trial.EmailFromName, t.Trial.ResearchProgramNo, t.TrialSiteCode, t.TrialSiteName, t.TrialSiteAliasName }).FirstOrDefaultAsync();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(name, email));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.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(ReplaceCompanyName(input.htmlBodyStr),
name,
trialInfo.TrialCode,
trialInfo.TrialSiteCode,
trialInfo.TrialSiteName,
url
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_UpdateUser, messageToSend, emailConfigFunc);
SendEmailHelper.ChangeEmailSenderName(messageToSend, trialInfo.EmailFromName);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
}
}
@@ -81,7 +81,7 @@ namespace IRaCIS.Core.Application.Service
}
[AllowAnonymous]
public async Task<IResponseOutput<PublishLogView> > GetCurrentPublishInfo([FromServices] IOptionsMonitor<SystemEmailSendConfig> _sysEmialConfigOPtion)
public async Task<PublishLogView> GetCurrentPublishInfo([FromServices] IOptionsMonitor<SystemEmailSendConfig> _sysEmialConfigOPtion)
{
var result = await _publishLogRepository.Where(t => t.IsCurrentVersion == true).ProjectTo<PublishLogView>(_mapper.ConfigurationProvider).FirstOrDefaultAsync();
@@ -92,13 +92,7 @@ namespace IRaCIS.Core.Application.Service
}
result.IsEnv_US = _sysEmialConfigOPtion.CurrentValue.IsEnv_US;
var emailConfig = _sysEmialConfigOPtion.CurrentValue;
var companyInfo = new SystemEmailSendConfigView() { CompanyName = emailConfig.CompanyName, CompanyNameCN = emailConfig.CompanyNameCN, CompanyShortName = emailConfig.CompanyShortName, CompanyShortNameCN = emailConfig.CompanyShortNameCN, SystemShortName = emailConfig.SystemShortName, EmailRegexStr = emailConfig.EmailRegexStr };
return ResponseOutput.Ok(result, companyInfo) ;
return result;
}
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,6 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service.Common;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Service
@@ -11,18 +10,7 @@ namespace IRaCIS.Core.Application.Service
{
public CommonConfig()
{
// 在此处拷贝automapper 映射
CreateMap<EmailAttachmentLog, EmaliAttachmentInfo>();
CreateMap<EmailRecipientLog, EmailRecipientLogView>();
CreateMap<EmailLog, EmailLogView>()
.ForMember(t => t.RecipientList, u => u.MapFrom(c => c.EmailRecipientLogList))
.ForMember(t => t.AttachmentList, u => u.MapFrom(c => c.AttachmentList));
CreateMap<EmailLog, GetEmailInfoOutDto>()
.ForMember(t => t.RecipientList, u => u.MapFrom(c => c.EmailRecipientLogList))
.ForMember(t => t.AttachmentList, u => u.MapFrom(c => c.AttachmentList));
CreateMap<EmailLog, EmailLogAddOrEdit>().ReverseMap();
CreateMap<FrontAuditConfig, FrontAuditConfigAddOrEdit>().ReverseMap();
@@ -116,10 +104,6 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.Subject.TrialSite.TrialSiteCode));
CreateMap<TumorExportBaseModel , TU_Export>();
CreateMap<TumorExportBaseModel, TR_Export>();
CreateMap<TumorExportBaseModel, RS_Export>();
CreateMap<TumorExportBaseModel, CO_Export>();
}
}
@@ -242,8 +242,6 @@ namespace IRaCIS.Application.Contracts
public class ConfirmationReviewerDTO : DoctorOptDTO
{
public Guid DoctorId { get; set; }
public int DoctorTrialState { get; set; }
public string OptUserName { get; set; } = string.Empty;
@@ -251,11 +249,6 @@ namespace IRaCIS.Application.Contracts
public DateTime? OptTime { get; set; }
public string? OptTimeStr => OptTime?.ToString("yyyy-MM-dd HH:mm:ss");
public UserTypeEnum? AuditTypeEnum { get; set; }
public DateTime? AuditTime { get; set; }
public string AuditUserName { get; set; } = string.Empty;
public string? AuditTimeStr => AuditTime?.ToString("yyyy-MM-dd HH:mm:ss");
//SPM 需要看到
public DateTime? SubmmitTime { get; set; }
public string SubmmitUserName { get; set; } = string.Empty;
@@ -275,8 +268,6 @@ namespace IRaCIS.Application.Contracts
public Guid? HospitalId { get; set; }
public Guid EnrollId { get; set; }
}
@@ -302,6 +302,8 @@ namespace IRaCIS.Core.Application.Service
.Where(x => x.TrialId == inQuery.TrialId && x.EnrollStatus >= EnrollStatus.InviteIntoGroup);
var enrollStateList = await query
.ProjectTo<DoctorStateModelDTO>(_mapper.ConfigurationProvider).ToListAsync();
enrollStateList = enrollStateList.GroupBy(e => e.DoctorId)
.Select(g => g.OrderByDescending(e => e.OptTime).FirstOrDefault()).ToList();
enrollStateList = enrollStateList
.WhereIf(inQuery.OptStartTime != null, x => x.OptTime >= inQuery.OptStartTime)
@@ -335,29 +337,14 @@ namespace IRaCIS.Core.Application.Service
doctorPageList.CurrentPageData.ToList().ForEach(u =>
{
u.DoctorTrialState = (int)EnrollStatus.InviteIntoGroup;
var lastState= enrollStateList.OrderByDescending(x=>x.OptTime).FirstOrDefault(t => t.DoctorId == u.Id);
if (lastState != null)
{
u.DoctorTrialState = lastState.IntoGroupState;
}
var opt = enrollStateList.Where(x => x.IntoGroupState == 10).FirstOrDefault(t => t.DoctorId == u.Id);
var opt = enrollStateList.OrderByDescending(x=>x.OptTime).FirstOrDefault(t => t.DoctorId == u.Id);
if (opt != null)
{
u.DoctorTrialState = opt.IntoGroupState;
u.OptTime = opt.OptTime;
u.UserTypeEnum = opt.UserTypeEnum;
u.OptUserName = opt.OptUserName;
}
var approved = enrollStateList.Where(x => x.IntoGroupState == 8).FirstOrDefault(t => t.DoctorId == u.Id);
if (approved != null)
{
u.AuditTime = approved.OptTime;
u.AuditTypeEnum = approved.UserTypeEnum;
u.AuditUserName = approved.OptUserName;
}
});
return doctorPageList;
@@ -3,7 +3,6 @@ using AutoMapper.EquivalencyExpression;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Contracts.Pay;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Service
@@ -213,8 +212,6 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.Code, u => u.MapFrom(s => s.DoctorUser.UserName))
.ForMember(d => d.HospitalName, u => u.MapFrom(s => s.Doctor.HospitalName))
.ForMember(d => d.HospitalNameCN, u => u.MapFrom(s => s.Doctor.HospitalNameCN))
.ForMember(d => d.EnrollId, u => u.MapFrom(s => s.Id))
;
CreateMap<Doctor, ConfirmationReviewerDTO>();
CreateMap<Hospital, ConfirmationReviewerDTO>();
File diff suppressed because it is too large Load Diff
@@ -9,99 +9,6 @@ using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.ViewModel;
public class AuditRecordView : AuditRecordAddOrEdit
{
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public new List<Guid> IdnetityUserIdList => IdentityUserList.Select(t => t.Id).ToList();
public List<AuditIdentiUserInfo> IdentityUserList { get; set; }
}
public class AuditIdentiUserInfo
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
}
public class AuditRecordAddOrEdit
{
public Guid? Id { get; set; }
public string CompanyName { get; set; }
public string AuditContent { get; set; }
public DateOnly AuditTime { get; set; }
public DateTime? BeginTime { get; set; }
public DateTime? EndTime { get; set; }
public AuditState AuditState { get; set; }
public AuditType AuditType { get; set; }
public bool IsViewTrainingRecord { get; set; }
//public List<Guid> IdnetityUserIdList { get; set; }
}
public class AddOrDeleteAuditUserCommand
{
public Guid AuditRecordId { get; set; }
public List<Guid> IdentityUserIdList { get; set; }
public bool IsAdd { get; set; }
}
public class AuditRecordQuery : PageInput
{
public string? CompanyName { get; set; }
public string? AuditContent { get; set; }
public DateOnly? BeginAuditTime { get; set; }
public DateOnly? EndAuditTime { get; set; }
public DateTime? BeginTime { get; set; }
public DateTime? EndTime { get; set; }
public AuditState? AuditState { get; set; }
public AuditType? AuditType { get; set; }
public DateTime? BeginCreateTime { get; set; }
public DateTime? EndCreateTime { get; set; }
public string? IdentityUserName { get; set; }
public bool? IsViewTrainingRecord { get; set; }
}
public class SetAuditRecordPermissionCommand
{
public List<Guid> AuditDocumentIdList { get; set; }
public Guid AuditRecordId { get; set; }
public bool IsAuthorization { get; set; }
}
public class DeleteAudit
{
public Guid Id { get; set; }
@@ -117,21 +24,16 @@ public class DeleteAuditDocumentInDto
public List<Guid> Ids { get; set; }
}
public class GetAuditDocumentDataInDto : PageInput
public class GetAuditDocumentDataInDto:PageInput
{
public Guid? Id { get; set; }
public Guid? Id { get; set; }
public Guid? SelfId { get; set; }
public bool? IsAuthorization { get; set; }
public bool? IsCurrentAuditRecordAuthorization { get; set; }
public string Name { get; set; } = string.Empty;
//当前稽查记录Id
public Guid? AuditRecordId { get; set; }
}
public class GetAuditDocumentDataOutDto
@@ -151,20 +53,17 @@ public class AuditDocumentData : AuditDocumentUpdateDto
public int? Version { get; set; }
public int HistoricalVersionsCount { get; set; }
public List<AuditDocumentData> Children { get; set; } = new List<AuditDocumentData>() { };
public bool? IsCurrentAuditRecordAuthorization { get; set; }
public List<AuditDocumentData> Children { get; set; }=new List<AuditDocumentData> (){ };
}
public class AuditDocumentView : AuditDocumentAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class SetIsAuthorizationInDto
@@ -181,7 +80,7 @@ public class SetCurrentVersionInDto
public class GetHistoricalVersionInDto
{
public Guid Id { get; set; }
public Guid Id { get; set; }
}
public class GetHistoricalVersionOutDto
@@ -194,10 +93,10 @@ public class MovieFileOrFolderInDto
{
public List<Guid> Ids { get; set; }
public Guid? ParentId { get; set; }
public Guid ParentId { get; set; }
}
public class HistoricalVersionDto : AuditDocumentUpdateDto
public class HistoricalVersionDto: AuditDocumentUpdateDto
{
public bool IsCurrentVersion { get; set; } = false;
@@ -214,7 +113,7 @@ public class AuditDocumentUpdateDto
public AuditDocumentType AuditDocumentTypeEnum { get; set; }
public string? FileFormat { get; set; }
public string? FileFormat { get; set; }
public string? FilePath { get; set; }
@@ -234,34 +133,34 @@ public class GetBreadcrumbDataInDto
public Guid Id { get; set; }
}
public class AuditDocumentAddOrEdit : AuditDocumentUpdateDto
public class AuditDocumentAddOrEdit: AuditDocumentUpdateDto
{
public List<AuditDocumentAddOrEdit> Children { get; set; } = new List<AuditDocumentAddOrEdit>() { };
public List<AuditDocumentAddOrEdit> Children { get; set; }=new List<AuditDocumentAddOrEdit>() { };
public bool IsUpdate { get; set; } = true;
}
public class AuditDocumentQuery : PageInput
public class AuditDocumentQuery:PageInput
{
public AuditDocumentType? AuditDocumentTypeEnum { get; set; }
public string? FileFormat { get; set; }
public string? FilePath { get; set; }
public decimal? FileSize { get; set; }
public decimal? FileSize { get; set; }
public bool? IsAuthorization { get; set; }
public Guid? MainFileId { get; set; }
public Guid? MainFileId { get; set; }
public string? Name { get; set; }
public Guid? ParentId { get; set; }
}
public Guid? ParentId { get; set; }
}

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