Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4ad1c0653 | |||
| 84df49ca07 | |||
| 400edd0b70 | |||
| c4add1ecc8 | |||
| d5879e28d7 | |||
| 37e083face | |||
| 290506a060 | |||
| 1b4bc3690a | |||
| 6374781d26 | |||
| b28013a02f | |||
| fe429d2bdc | |||
| d63af2ed20 | |||
| cebf9875b1 | |||
| 131681a0b3 | |||
| f833adc710 | |||
| df80d1e551 | |||
| 82dc092247 | |||
| 44894fcaa9 | |||
| 083bebdc03 | |||
| e808764dd6 | |||
| 77d198cb09 | |||
| 180b02b28c | |||
| c49a74d742 | |||
| c76fc8f052 | |||
| 14875dfacc | |||
| 0a68788c0f | |||
| b17e5d9ff3 | |||
| 05ce4b7155 | |||
| c8bbb3da48 | |||
| 65de108ecd | |||
| d20c306219 | |||
| e69991ae37 | |||
| e1e2544ee3 | |||
| 318333bae4 | |||
| d569de4dfe | |||
| 05627ff126 | |||
| 627618c854 | |||
| 2711c5eb04 | |||
| a6982f79c3 | |||
| 2a542949cd | |||
| a8ccbce76f | |||
| df7cf30aed | |||
| 0664fbdfb7 | |||
| 0a6ee05417 | |||
| f9bd8c3f5c | |||
| cb83c2a737 | |||
| fdd9afd4f0 | |||
| 53d26c0445 | |||
| 40d035e7a7 | |||
| a13807ae5f | |||
| 9d5aaf1e26 | |||
| c347d08e2b | |||
| 87f2d0e429 | |||
| 89d009fcbf | |||
| 7d65cf5051 | |||
| e74427c45c | |||
| e8825c7efa | |||
| 628e0ad034 | |||
| 2f3f639918 | |||
| 87caf24dd1 |
@@ -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();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -113,9 +113,6 @@ builder.Services.AddAutoMapper(automapper =>
|
||||
//EF ORM QueryWithNoLock
|
||||
builder.Services.AddEFSetup(_configuration);
|
||||
|
||||
// FusionCache
|
||||
builder.Services.AddFusionCache();
|
||||
|
||||
builder.Services.AddMediator(cfg =>
|
||||
{
|
||||
|
||||
@@ -194,7 +191,6 @@ app.MapControllers();
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
//.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging;
|
||||
using FellowOakDicom.IO.Buffer;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using Medallion.Threading;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using FellowOakDicom.Network;
|
||||
using FellowOakDicom;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Medallion.Threading;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Serilog;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Data;
|
||||
using FellowOakDicom.Imaging;
|
||||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
@@ -42,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; }
|
||||
|
||||
@@ -52,8 +47,6 @@ namespace IRaCIS.Core.SCP.Service
|
||||
|
||||
private Guid _trialSiteId { get; set; }
|
||||
|
||||
private bool _releasedNormally = false;
|
||||
|
||||
|
||||
|
||||
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
|
||||
@@ -121,7 +114,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
|
||||
|
||||
|
||||
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId == _trialId).FirstOrDefault();
|
||||
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
|
||||
|
||||
if (findTrialSiteAE != null)
|
||||
{
|
||||
@@ -130,7 +123,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||
isCanReceiveIamge = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (association.CallingAE == "test-callingAE")
|
||||
@@ -169,61 +162,27 @@ namespace IRaCIS.Core.SCP.Service
|
||||
|
||||
public async Task OnReceiveAssociationReleaseRequestAsync()
|
||||
{
|
||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||
await DataMaintenanceAsaync();
|
||||
|
||||
var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}");
|
||||
|
||||
using (await @lock.AcquireAsync())
|
||||
{
|
||||
|
||||
await DataMaintenanceAsaync();
|
||||
|
||||
//记录监控
|
||||
|
||||
await AddUploadLogAsync();
|
||||
|
||||
|
||||
_releasedNormally = true;
|
||||
|
||||
Log.Logger.Information($"进入释放连接请求 {_releasedNormally}");
|
||||
|
||||
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
////将检查设置为传输结束
|
||||
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
|
||||
|
||||
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
|
||||
}
|
||||
|
||||
await SendAssociationReleaseResponseAsync();
|
||||
|
||||
}
|
||||
|
||||
private async Task AddUploadLogAsync()
|
||||
{
|
||||
//记录监控
|
||||
|
||||
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
|
||||
|
||||
//有时候没有建立连接会走关闭通过日志发现
|
||||
if (_upload != null)
|
||||
{
|
||||
_upload.EndTime = DateTime.Now;
|
||||
_upload.StudyCount = _ImageUploadList.Count;
|
||||
_upload.TrialId = _trialId;
|
||||
_upload.TrialSiteId = _trialSiteId;
|
||||
_upload.EndTime = DateTime.Now;
|
||||
_upload.StudyCount = _SCPStudyIdList.Count;
|
||||
_upload.TrialId = _trialId;
|
||||
_upload.TrialSiteId = _trialSiteId;
|
||||
|
||||
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
|
||||
|
||||
//可能是测试echo 导致记录了
|
||||
if (_upload.FileCount > 0)
|
||||
{
|
||||
//可能是测试echo 导致记录了
|
||||
await _SCPImageUploadRepository.AddAsync(_upload, true);
|
||||
}
|
||||
}
|
||||
await _SCPImageUploadRepository.AddAsync(_upload, true);
|
||||
|
||||
|
||||
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
//将检查设置为传输结束
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
|
||||
|
||||
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
|
||||
await SendAssociationReleaseResponseAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -278,25 +237,17 @@ namespace IRaCIS.Core.SCP.Service
|
||||
|
||||
public async void OnConnectionClosed(Exception exception)
|
||||
{
|
||||
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
/* nothing to do here */
|
||||
|
||||
|
||||
if (exception != null || _releasedNormally == false)
|
||||
//奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致
|
||||
if (exception == null)
|
||||
{
|
||||
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭
|
||||
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
|
||||
////将检查设置为传输结束
|
||||
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
|
||||
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true });
|
||||
//记录日志
|
||||
await AddUploadLogAsync();
|
||||
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
//将检查设置为传输结束
|
||||
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false });
|
||||
}
|
||||
|
||||
|
||||
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
|
||||
|
||||
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
|
||||
}
|
||||
@@ -307,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());
|
||||
@@ -335,11 +272,6 @@ namespace IRaCIS.Core.SCP.Service
|
||||
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
||||
|
||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||
var _fusionCache = _serviceProvider.GetService<IFusionCache>();
|
||||
var _trialSiteRepository = _serviceProvider.GetService<IRepository<TrialSite>>();
|
||||
var _systemAnonymizationRepository = _serviceProvider.GetService<IRepository<SystemAnonymization>>();
|
||||
|
||||
|
||||
|
||||
var storeRelativePath = string.Empty;
|
||||
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
|
||||
@@ -348,383 +280,15 @@ namespace IRaCIS.Core.SCP.Service
|
||||
long fileSize = 0;
|
||||
try
|
||||
{
|
||||
// 直接拿 Dataset(已经完整)
|
||||
var dataset = request.Dataset;
|
||||
|
||||
#region 匿名化
|
||||
|
||||
|
||||
var anonymizeList = await _fusionCache.GetOrSetAsync(CacheKeys.SystemAnonymization, _ => CacheHelper.GetSystemAnonymizationListAsync(_systemAnonymizationRepository), TimeSpan.FromDays(7));
|
||||
|
||||
var trialSiteInfo = await _fusionCache.GetOrSetAsync(CacheKeys.TrialSiteInfo(_trialSiteId), _ => CacheHelper.GetTrialSiteInfo(_trialSiteId, _trialSiteRepository), TimeSpan.FromMinutes(2));
|
||||
|
||||
var fixedFiledList = anonymizeList.Where(t => t.IsFixed).ToList();
|
||||
|
||||
var ircFiledList = anonymizeList.Where(t => t.IsFixed == false).ToList();
|
||||
|
||||
foreach (var item in fixedFiledList)
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
await request.File.SaveAsync(ms);
|
||||
|
||||
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
|
||||
|
||||
dataset.AddOrUpdate(dicomTag, item.ReplaceValue);
|
||||
}
|
||||
|
||||
foreach (var item in ircFiledList)
|
||||
{
|
||||
|
||||
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
|
||||
|
||||
if (dicomTag == DicomTag.ClinicalTrialProtocolID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialProtocolID, trialSiteInfo.TrialCode);
|
||||
|
||||
}
|
||||
if (dicomTag == DicomTag.ClinicalTrialSiteID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialSiteID, trialSiteInfo.TrialSiteCode);
|
||||
|
||||
}
|
||||
|
||||
if (dicomTag == DicomTag.ClinicalTrialSubjectID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialSubjectID, "");
|
||||
|
||||
}
|
||||
if (dicomTag == DicomTag.ClinicalTrialTimePointID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialTimePointID, "");
|
||||
}
|
||||
|
||||
if (dicomTag == DicomTag.PatientID)
|
||||
{
|
||||
var pid = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||||
|
||||
dataset.AddOrUpdate(DicomTag.PatientID, trialSiteInfo.TrialCode + "-" + pid);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 构造 DicomFile(不用 Open)
|
||||
var dicomFile = new DicomFile(dataset);
|
||||
|
||||
|
||||
|
||||
#region 1帧拆成多个固定大小的,方便移动端浏览
|
||||
|
||||
|
||||
|
||||
var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
|
||||
|
||||
//多帧处理逻辑
|
||||
if (numberOfFrames > 1)
|
||||
{
|
||||
//一定要有像素数据才处理
|
||||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||||
|
||||
if (pixelData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 开始处理多帧instanceId:{instanceId}");
|
||||
|
||||
var syntax = pixelData.Syntax;
|
||||
|
||||
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
|
||||
int fragmentSize = 20 * 1024;
|
||||
|
||||
|
||||
|
||||
var frag = dicomFile.Dataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData);
|
||||
|
||||
int fragmentCount = frag?.Fragments?.Count() ?? 0;
|
||||
|
||||
var originOffsetTable = frag?.OffsetTable; //有可能没有表,需要自己重建
|
||||
|
||||
var bot = new List<uint>();
|
||||
|
||||
uint botOffset = 0;
|
||||
|
||||
//需要拆成固定片段的
|
||||
if (syntax.IsEncapsulated && fragmentCount == pixelData.NumberOfFrames && numberOfFrames > 1)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
|
||||
|
||||
#region test
|
||||
//var newDicomFile = dicomFile.Clone();
|
||||
|
||||
//var newDataset = newDicomFile.Dataset;
|
||||
|
||||
//var dstPd = DicomPixelData.Create(newDataset, true);
|
||||
|
||||
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
|
||||
//{
|
||||
// var frame = pixelData.GetFrame(i);
|
||||
|
||||
// dstPd.AddFrame(frame);
|
||||
|
||||
// var data = frame.Data;
|
||||
// int offset = 0;
|
||||
|
||||
// while (offset < data.Length)
|
||||
// {
|
||||
// int size = Math.Min(fragmentSize, data.Length - offset);
|
||||
// var buffer = new byte[size];
|
||||
// Buffer.BlockCopy(data, offset, buffer, 0, size);
|
||||
|
||||
// newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
|
||||
|
||||
// offset += size;
|
||||
|
||||
// }
|
||||
|
||||
|
||||
//}
|
||||
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
|
||||
|
||||
//newFragments.OffsetTable.AddRange(newOffsetTable.ToArray());
|
||||
#endregion
|
||||
|
||||
#region test fo-dicom auto bot
|
||||
//var newDicomFile = dicomFile.Clone();
|
||||
|
||||
//var newDataset = newDicomFile.Dataset;
|
||||
|
||||
//var dstPd = DicomPixelData.Create(newDataset, true);
|
||||
|
||||
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
|
||||
//{
|
||||
// var frame = pixelData.GetFrame(i);
|
||||
|
||||
// dstPd.AddFrame(frame);
|
||||
//}
|
||||
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
|
||||
|
||||
//Console.WriteLine(newOffsetTable.ToJsonStr());
|
||||
#endregion
|
||||
|
||||
#region 最终使用
|
||||
|
||||
for (int n = 0; n < pixelData.NumberOfFrames; n++)
|
||||
{
|
||||
var frameData = pixelData.GetFrame(n); // 获取完整一帧
|
||||
var data = frameData.Data;
|
||||
int offset = 0;
|
||||
|
||||
bot.Add(botOffset);
|
||||
|
||||
botOffset += (uint)data.Length;
|
||||
|
||||
|
||||
while (offset < data.Length)
|
||||
{
|
||||
botOffset += 8;
|
||||
|
||||
|
||||
int size = Math.Min(fragmentSize, data.Length - offset);
|
||||
var buffer = new byte[size];
|
||||
Buffer.BlockCopy(data, offset, buffer, 0, size);
|
||||
|
||||
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
|
||||
|
||||
offset += size;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//保留原始偏移表
|
||||
|
||||
if (originOffsetTable.Count == pixelData.NumberOfFrames)
|
||||
{
|
||||
newFragments.OffsetTable.AddRange(originOffsetTable.ToArray());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
newFragments.OffsetTable.AddRange(bot.ToArray());
|
||||
|
||||
//Console.WriteLine(bot.ToJsonStr());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
dicomFile.Dataset.AddOrUpdate(newFragments);
|
||||
|
||||
|
||||
}
|
||||
//传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败
|
||||
else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0)
|
||||
{
|
||||
|
||||
|
||||
|
||||
uint offset = 0;
|
||||
|
||||
bot.Add(offset);
|
||||
|
||||
var fistSize = frag.Fragments.FirstOrDefault()?.Size ?? 0;
|
||||
|
||||
//和上一个大小不一样
|
||||
var isDiffrentBefore = false;
|
||||
|
||||
uint count = 0;
|
||||
|
||||
// 假设你知道每帧对应的 fragment 数量
|
||||
foreach (var frameFragments in frag.Fragments)
|
||||
{
|
||||
count++;
|
||||
|
||||
if (frameFragments.Size == fistSize)
|
||||
{
|
||||
isDiffrentBefore = false;
|
||||
// 累加这一帧所有 fragment 的大小
|
||||
offset += (uint)frameFragments.Size;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += (uint)frameFragments.Size;
|
||||
isDiffrentBefore = true;
|
||||
|
||||
}
|
||||
|
||||
if (isDiffrentBefore)
|
||||
{
|
||||
//每个Fragment 也占用字节
|
||||
offset += 8 * count;
|
||||
bot.Add(offset);
|
||||
count = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bot.RemoveAt(bot.Count - 1);
|
||||
// 设置到新的 PixelData
|
||||
frag.OffsetTable.AddRange(bot.ToArray());
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception mutiEx)
|
||||
{
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 处理多帧失败,上传原始文件:{mutiEx.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 本地测试
|
||||
//// --- 保存到本地文件测试 ---
|
||||
//var localPath = @"D:\TestDicom.dcm";
|
||||
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
|
||||
//{
|
||||
// ms.CopyTo(fs);
|
||||
//}
|
||||
//return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||
|
||||
#endregion
|
||||
|
||||
// 直接写入内存
|
||||
await using var ms = new MemoryStream();
|
||||
await dicomFile.SaveAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
//irc 从路径最后一截取Guid
|
||||
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
|
||||
|
||||
fileSize = ms.Length;
|
||||
|
||||
|
||||
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
|
||||
|
||||
using (await @lock.AcquireAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(dicomFile, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE, fileSize);
|
||||
|
||||
|
||||
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
|
||||
//没有缩略图
|
||||
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
|
||||
{
|
||||
|
||||
// 生成缩略图
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
DicomImage image = new DicomImage(dicomFile.Dataset);
|
||||
|
||||
var sharpimage = image.RenderImage().AsSharpImage();
|
||||
sharpimage.Save(memoryStream, new JpegEncoder());
|
||||
|
||||
// 上传缩略图到 OSS
|
||||
|
||||
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, $"{seriesId.ToString()}_{instanceId.ToString()}.preview.jpg", false);
|
||||
|
||||
|
||||
series.ImageResizePath = seriesPath;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await _seriesRepository.SaveChangesAsync();
|
||||
|
||||
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||
{
|
||||
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
|
||||
|
||||
|
||||
find.SuccessImageCount++;
|
||||
|
||||
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
|
||||
{
|
||||
find.PatientNameList.Add(patientIdStr);
|
||||
}
|
||||
|
||||
//首次 (默认是Guid 空,数据库归档出了Id)
|
||||
if (find.SCPStudyId != scpStudyId)
|
||||
{
|
||||
find.SCPStudyId = scpStudyId;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//监控信息设置
|
||||
_upload.FileCount++;
|
||||
_upload.FileSize = _upload.FileSize + fileSize;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
|
||||
|
||||
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
|
||||
{
|
||||
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
|
||||
|
||||
find.FailedImageCount++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//irc 从路径最后一截取Guid
|
||||
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
|
||||
|
||||
fileSize = ms.Length;
|
||||
}
|
||||
|
||||
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
|
||||
@@ -735,6 +299,63 @@ namespace IRaCIS.Core.SCP.Service
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
|
||||
|
||||
using (await @lock.AcquireAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
|
||||
|
||||
if (!_SCPStudyIdList.Contains(scpStudyId))
|
||||
{
|
||||
_SCPStudyIdList.Add(scpStudyId);
|
||||
}
|
||||
|
||||
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
|
||||
//没有缩略图
|
||||
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
|
||||
{
|
||||
|
||||
// 生成缩略图
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
DicomImage image = new DicomImage(request.Dataset);
|
||||
|
||||
var sharpimage = image.RenderImage().AsSharpImage();
|
||||
sharpimage.Save(memoryStream, new JpegEncoder());
|
||||
|
||||
// 上传缩略图到 OSS
|
||||
|
||||
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
|
||||
|
||||
Console.WriteLine(seriesPath + " Id: " + seriesId);
|
||||
|
||||
series.ImageResizePath = seriesPath;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await _seriesRepository.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//监控信息设置
|
||||
_upload.FileCount++;
|
||||
_upload.FileSize = _upload.FileSize + fileSize;
|
||||
return new DicomCStoreResponse(request, DicomStatus.Success);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service;
|
||||
|
||||
|
||||
public static class CacheKeys
|
||||
{
|
||||
//项目缓存
|
||||
public static string Trial(string trialIdStr) => $"TrialId:{trialIdStr}";
|
||||
|
||||
//检查编号递增锁
|
||||
public static string TrialStudyMaxCode(Guid trialId) => $"TrialStudyMaxCode:{trialId}";
|
||||
|
||||
public static string TrialStudyUidUploading(Guid trialId, string studyUid) => $"TrialStudyUid:{trialId}_{studyUid}";
|
||||
//CRC上传影像提交锁key
|
||||
public static string TrialStudyUidDBLock(Guid trialId, string studyUid) => $"TrialStudyUidDBLock:{trialId}_{studyUid}";
|
||||
|
||||
public static string TrialTaskStudyUidUploading(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialStudyUid:{trialId}_{visiTaskId}_{studyUid}";
|
||||
//影像后处理上传提交锁key
|
||||
public static string TrialTaskStudyUidDBLock(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialTaskStudyUidDBLock:{trialId}_{visiTaskId}_{studyUid}";
|
||||
//系统匿名化
|
||||
public static string SystemAnonymization => $"SystemAnonymization";
|
||||
//前端国际化
|
||||
public static string FrontInternational => $"FrontInternationalList";
|
||||
|
||||
//登录挤账号
|
||||
public static string UserToken(Guid userId) => $"UserToken:{userId}";
|
||||
|
||||
//超时没请求接口自动退出
|
||||
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
|
||||
|
||||
|
||||
public static string UserDisable(Guid userId) => $"UserDisable:{userId}";
|
||||
|
||||
public static string UserRoleDisable(Guid userRoleId) => $"UserRoleDisable:{userRoleId}";
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录错误 限制登录
|
||||
/// </summary>
|
||||
/// <param name="userName"></param>
|
||||
/// <returns></returns>
|
||||
public static string UserLoginError(string userName) => $"login-failures:{userName}";
|
||||
|
||||
/// <summary>
|
||||
/// 跳过阅片
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string SkipReadingCacheKey(Guid userId) => $"{userId}SkipReadingCache";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开始阅片时间
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string StartReadingTimeKey(Guid userId) => $"{userId}StartReadingTime";
|
||||
|
||||
/// <summary>
|
||||
/// 开始休息时间
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
|
||||
|
||||
//每个用户 每个浏览器独立时间
|
||||
public static string UserMFAVerifyPass(Guid userId, string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
|
||||
|
||||
public static string TrialSiteInfo(Guid trialSiteId) => $"{trialSiteId}TrialSiteInfo";
|
||||
}
|
||||
|
||||
public static class CacheHelper
|
||||
{
|
||||
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
|
||||
{
|
||||
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync();
|
||||
|
||||
return statusStr;
|
||||
}
|
||||
|
||||
public class TrialSiteInfoDTO
|
||||
{
|
||||
public string TrialSiteCode { get; set; }
|
||||
public string TrialCode { get; set; }
|
||||
}
|
||||
public static async Task<TrialSiteInfoDTO> GetTrialSiteInfo(Guid trialSiteId, IRepository<TrialSite> _trialSiteRepository)
|
||||
{
|
||||
var obj = await _trialSiteRepository.Where(t => t.Id == trialSiteId, ignoreQueryFilters: true).Select(t => new TrialSiteInfoDTO { TrialCode= t.Trial.TrialCode, TrialSiteCode= t.TrialSiteCode }).FirstOrDefaultAsync();
|
||||
|
||||
return obj??new TrialSiteInfoDTO();
|
||||
}
|
||||
|
||||
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
|
||||
{
|
||||
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,48 @@
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging.Codec;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using MassTransit;
|
||||
using Medallion.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Serilog.Sinks.File;
|
||||
using System.Data;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using System.Text;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using static IRaCIS.Core.SCP.Service.CacheHelper;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Medallion.Threading;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging.Codec;
|
||||
using System.Data;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using MassTransit;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Serilog.Sinks.File;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
public class DicomArchiveService(IRepository<SCPPatient> _patientRepository,
|
||||
IRepository<SCPStudy> _studyRepository,
|
||||
IRepository<SCPSeries> _seriesRepository,
|
||||
IRepository<SCPInstance> _instanceRepository
|
||||
) : BaseService, IDicomArchiveService
|
||||
public class DicomArchiveService : BaseService, IDicomArchiveService
|
||||
{
|
||||
private readonly IRepository<SCPPatient> _patientRepository;
|
||||
private readonly IRepository<SCPStudy> _studyRepository;
|
||||
private readonly IRepository<SCPSeries> _seriesRepository;
|
||||
private readonly IRepository<SCPInstance> _instanceRepository;
|
||||
private readonly IRepository<Dictionary> _dictionaryRepository;
|
||||
private readonly IDistributedLockProvider _distributedLockProvider;
|
||||
|
||||
|
||||
private List<Guid> _instanceIdList = new List<Guid>();
|
||||
|
||||
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
|
||||
IRepository<SCPSeries> seriesRepository,
|
||||
IRepository<SCPInstance> instanceRepository,
|
||||
IRepository<Dictionary> dictionaryRepository,
|
||||
IDistributedLockProvider distributedLockProvider)
|
||||
{
|
||||
_distributedLockProvider = distributedLockProvider;
|
||||
_studyRepository = studyRepository;
|
||||
_patientRepository = patientRepository;
|
||||
_seriesRepository = seriesRepository;
|
||||
_instanceRepository = instanceRepository;
|
||||
_dictionaryRepository = dictionaryRepository;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,19 +52,16 @@ namespace IRaCIS.Core.SCP.Service
|
||||
/// <param name="dataset"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE, long fileSize)
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
|
||||
{
|
||||
|
||||
var dataset = dicomFile.Dataset;
|
||||
|
||||
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
|
||||
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
|
||||
|
||||
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||||
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
|
||||
|
||||
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
|
||||
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
|
||||
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
|
||||
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
|
||||
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
|
||||
|
||||
@@ -61,15 +74,15 @@ namespace IRaCIS.Core.SCP.Service
|
||||
|
||||
//using (@lock.Acquire())
|
||||
{
|
||||
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId == trialSiteId);
|
||||
var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
|
||||
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
|
||||
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
|
||||
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
|
||||
|
||||
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
|
||||
|
||||
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
|
||||
if (findPatient == null /*&& findStudy==null*/)
|
||||
if (findPatient == null && findStudy==null)
|
||||
{
|
||||
isPatientNeedAdd = true;
|
||||
|
||||
@@ -77,8 +90,8 @@ namespace IRaCIS.Core.SCP.Service
|
||||
findPatient = new SCPPatient()
|
||||
{
|
||||
Id = NewId.NextSequentialGuid(),
|
||||
TrialId = trialId,
|
||||
TrialSiteId = trialSiteId,
|
||||
TrialId=trialId,
|
||||
TrialSiteId=trialSiteId,
|
||||
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
|
||||
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
|
||||
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
|
||||
@@ -106,7 +119,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||
|
||||
DateTime birthDate;
|
||||
|
||||
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate, out birthDate))
|
||||
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
|
||||
{
|
||||
var patientAge = studyTime.Value.Year - birthDate.Year;
|
||||
// 如果生日还未到,年龄减去一岁
|
||||
@@ -138,32 +151,6 @@ namespace IRaCIS.Core.SCP.Service
|
||||
}
|
||||
|
||||
findPatient.LatestPushTime = DateTime.Now;
|
||||
findPatient.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
findPatient.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
|
||||
findPatient.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
|
||||
findPatient.PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty);
|
||||
|
||||
if (findPatient.PatientBirthDate.Length == 8)
|
||||
{
|
||||
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
|
||||
|
||||
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
|
||||
|
||||
int year = 0;
|
||||
|
||||
var canParse = int.TryParse(yearStr, out year);
|
||||
|
||||
if (canParse && year > 1900)
|
||||
{
|
||||
findPatient.PatientBirthDate = birthDateStr;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
findPatient.PatientBirthDate = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (findStudy == null)
|
||||
@@ -180,9 +167,6 @@ namespace IRaCIS.Core.SCP.Service
|
||||
TrialSiteId = trialSiteId,
|
||||
StudyInstanceUid = studyInstanceUid,
|
||||
StudyTime = studyTime,
|
||||
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
|
||||
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
|
||||
|
||||
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||||
//ModalityForEdit = modalityForEdit,
|
||||
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
|
||||
@@ -204,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,
|
||||
@@ -223,20 +201,6 @@ namespace IRaCIS.Core.SCP.Service
|
||||
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
findStudy.DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty);
|
||||
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
|
||||
findStudy.CalledAE = calledAE;
|
||||
findStudy.CallingAE = callingAE;
|
||||
findStudy.PatientIdStr = patientIdStr;
|
||||
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
|
||||
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
|
||||
findStudy.UpdateTime = DateTime.Now;
|
||||
|
||||
await _patientRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.PatientId, u => new SCPPatient() { LatestPushTime = DateTime.Now });
|
||||
}
|
||||
|
||||
|
||||
if (findSerice == null)
|
||||
@@ -254,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),
|
||||
@@ -272,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)
|
||||
{
|
||||
@@ -319,46 +264,22 @@ namespace IRaCIS.Core.SCP.Service
|
||||
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
|
||||
|
||||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||||
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1),
|
||||
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
|
||||
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
|
||||
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
|
||||
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
|
||||
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
|
||||
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
|
||||
|
||||
PhotometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty),
|
||||
BitsAllocated = dataset.GetSingleValueOrDefault(DicomTag.BitsAllocated, 0),
|
||||
PixelRepresentation = dataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, string.Empty),
|
||||
RescaleIntercept = dataset.GetSingleValueOrDefault(DicomTag.RescaleIntercept, string.Empty),
|
||||
RescaleSlope = dataset.GetSingleValueOrDefault(DicomTag.RescaleSlope, string.Empty),
|
||||
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
|
||||
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
|
||||
SequenceOfUltrasoundRegions = dataset.GetSingleValueOrDefault(DicomTag.SequenceOfUltrasoundRegions, string.Empty),
|
||||
FrameTime = dataset.GetSingleValueOrDefault(DicomTag.FrameTime, string.Empty),
|
||||
CorrectedImage = dataset.GetSingleValueOrDefault(DicomTag.CorrectedImage, string.Empty),
|
||||
Units = dataset.GetSingleValueOrDefault(DicomTag.Units, string.Empty),
|
||||
DecayCorrection = dataset.GetSingleValueOrDefault(DicomTag.DecayCorrection, string.Empty),
|
||||
EncapsulatedDocument = dataset.GetSingleValueOrDefault(DicomTag.EncapsulatedDocument, string.Empty),
|
||||
|
||||
|
||||
Path = fileRelativePath,
|
||||
|
||||
FileSize = fileSize,
|
||||
FileSize= fileSize,
|
||||
|
||||
};
|
||||
|
||||
++findStudy.InstanceCount;
|
||||
++findSerice.InstanceCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
findInstance.SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||||
findInstance.MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||||
findInstance.TransferSytaxUID = transferSyntaxUID;
|
||||
findInstance.MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||||
findInstance.IsEncapsulated = isEncapsulated;
|
||||
findInstance.UpdateTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (isPatientNeedAdd)
|
||||
{
|
||||
@@ -383,7 +304,7 @@ namespace IRaCIS.Core.SCP.Service
|
||||
}
|
||||
else
|
||||
{
|
||||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath, FileSize = fileSize });
|
||||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
|
||||
}
|
||||
|
||||
await _studyRepository.SaveChangesAsync();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
});
|
||||
@@ -279,7 +279,7 @@ namespace IRaCIS.Api.Controllers
|
||||
|
||||
var isExpire = _tokenService.IsTokenExpired(token);
|
||||
|
||||
if ( Guid.TryParse(userId,out _) == false || isExpire || !await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) )
|
||||
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire)
|
||||
{
|
||||
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} ";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts.DTO;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.Image.QA;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
@@ -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 8:QC 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));
|
||||
|
||||
@@ -15,13 +15,12 @@ using Hangfire.Storage;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MassTransit.Mediator;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace IRaCIS.Core.API.HostService;
|
||||
|
||||
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
IConfiguration _config,
|
||||
|
||||
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
|
||||
IMediator _mediator,
|
||||
ILogger<HangfireHostService> _logger) : IHostedService
|
||||
@@ -42,24 +41,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
|
||||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
}
|
||||
|
||||
// 清除所有可能存在的定时任务,防止类型加载错误
|
||||
var allJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Select(t => t.Id).ToList();
|
||||
foreach (var jobId in allJobIdList)
|
||||
{
|
||||
try
|
||||
{
|
||||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
_logger.LogInformation($"已清除定时任务: {jobId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"清除定时任务 {jobId} 时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
string defaultCulture = _config.GetValue<string>("SystemEmailSendConfig:CronEmailDefaultCulture");
|
||||
|
||||
// 项目手动选择,周期性邮件
|
||||
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
|
||||
.ToListAsync();
|
||||
@@ -71,11 +53,11 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
|
||||
|
||||
var trialId = task.TrialId;
|
||||
|
||||
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
|
||||
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
|
||||
}
|
||||
|
||||
|
||||
// 系统邮件定时任务 要设置默认语言
|
||||
// 系统邮件定时任务
|
||||
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
|
||||
.ToListAsync();
|
||||
@@ -85,7 +67,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
|
||||
//利用主键作为任务Id
|
||||
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
|
||||
|
||||
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
|
||||
HangfireJobHelper.AddOrUpdateSystemCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -22,7 +21,6 @@ using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -30,10 +28,6 @@ using System.Runtime.InteropServices;
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
|
||||
|
||||
|
||||
Console.WriteLine("Startup Culture: " + CultureInfo.CurrentCulture.Name);
|
||||
Console.WriteLine("Startup UI Culture: " + CultureInfo.CurrentUICulture.Name);
|
||||
|
||||
#region 获取环境变量
|
||||
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
|
||||
var config = new ConfigurationBuilder()
|
||||
@@ -101,18 +95,11 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc
|
||||
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
// 关键配置:禁用不可空引用类型的自动 Required 验证
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
|
||||
// 插到最前,抢在默认绑定器之前
|
||||
//options.ModelBinderProviders.Insert(0, new SpecificNullableBinderProvider());
|
||||
|
||||
options.Filters.Add<ModelActionFilter>();
|
||||
options.Filters.Add<ProjectExceptionFilter>();
|
||||
options.Filters.Add<UnitOfWorkFilter>();
|
||||
options.Filters.Add<LimitUserRequestAuthorization>();
|
||||
options.Filters.Add<TrialGlobalLimitActionFilter>();
|
||||
options.Filters.Add<RequestDuplicationFilter>();
|
||||
|
||||
})
|
||||
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://0.0.0.0:3305",
|
||||
"applicationUrl": "http://localhost:3305",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Test_IRC",
|
||||
"ASPNETCORE_OpenSwagger": "true"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"IRaCIS.Test_IRC_PGSQL": {
|
||||
"commandName": "Project",
|
||||
@@ -31,7 +31,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Test_IRC_PGSQL"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"IRaCIS.Event_IRC": {
|
||||
"commandName": "Project",
|
||||
@@ -39,7 +39,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Event_IRC"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
@@ -53,7 +53,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Uat_IRC"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"IRaCIS.Prod_IRC": {
|
||||
"commandName": "Project",
|
||||
@@ -61,7 +61,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Prod_IRC"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"IRaCIS.US_Uat_IRC": {
|
||||
"commandName": "Project",
|
||||
@@ -69,7 +69,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
},
|
||||
"IRaCIS.US_Prod_IRC": {
|
||||
"commandName": "Project",
|
||||
@@ -77,7 +77,7 @@
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "US_Prod_IRC"
|
||||
},
|
||||
"applicationUrl": "http://0.0.0.0:6100"
|
||||
"applicationUrl": "http://localhost:6100"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -39,8 +39,6 @@ namespace IRaCIS.Core.API._PipelineExtensions.Serilog
|
||||
|
||||
var requestBodyPayload = await ReadRequestBody(context.Request);
|
||||
|
||||
context.Items["RequestBody"] = requestBodyPayload;
|
||||
|
||||
using (LogContext.PushProperty("RequestBody", requestBodyPayload))
|
||||
{
|
||||
//await _next.Invoke()
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace IRaCIS.Core.API
|
||||
}
|
||||
else
|
||||
{
|
||||
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure()/*.CommandTimeout(60)*/);
|
||||
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -71,27 +71,29 @@ namespace IRaCIS.Core.API
|
||||
|
||||
DateTime dateTime;
|
||||
|
||||
// 2. 检查目标类型是否可空
|
||||
bool isNullable = objectType == typeof(DateTime?);
|
||||
|
||||
var canConvert = DateTime.TryParse(reader.Value?.ToString(), out dateTime);
|
||||
|
||||
|
||||
if (isNullable == false && canConvert == false)
|
||||
if (reader.ValueType == typeof(DateTime) || reader.ValueType == typeof(DateTime?))
|
||||
{
|
||||
DateTime? nullableDateTime = reader.Value as DateTime?;
|
||||
|
||||
throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}");
|
||||
|
||||
if (nullableDateTime != null && nullableDateTime.HasValue)
|
||||
{
|
||||
dateTime = nullableDateTime.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canConvert == false)
|
||||
if (DateTime.TryParse((string)reader.Value, out dateTime) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
////如果前端传递带时区的,那么转换会报错,需要DateTimeKind.Unspecified
|
||||
//dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
|
||||
|
||||
|
||||
// 将客户端时间转换为服务器时区的时间
|
||||
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
|
||||
@@ -111,10 +113,7 @@ namespace IRaCIS.Core.API
|
||||
//第一个参数默认使用系统本地时区 也就是应用服务器的时区
|
||||
DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone);
|
||||
|
||||
|
||||
//// 最简单的方式:创建 DateTimeOffset
|
||||
//DateTimeOffset dateTimeOffset = new DateTimeOffset(clientZoneTime, _clientTimeZone.GetUtcOffset(clientZoneTime));
|
||||
//writer.WriteValue(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:sszzz"));
|
||||
//writer.WriteValue(clientZoneTime);
|
||||
|
||||
writer.WriteValue(clientZoneTime.ToString(_dateFormat));
|
||||
}
|
||||
@@ -127,51 +126,6 @@ namespace IRaCIS.Core.API
|
||||
}
|
||||
|
||||
|
||||
public class DateOnlyUniversalJsonConverter : JsonConverter
|
||||
{
|
||||
private readonly string _format;
|
||||
|
||||
public DateOnlyUniversalJsonConverter(string format = "yyyy-MM-dd")
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(DateOnly) || objectType == typeof(DateOnly?);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteValue(""); // null -> 空字符串
|
||||
return;
|
||||
}
|
||||
|
||||
var date = (DateOnly)value;
|
||||
if (date == default)
|
||||
{
|
||||
writer.WriteValue(""); // default(DateOnly) -> 空字符串
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(date.ToString(_format));
|
||||
}
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var str = reader.TokenType == JsonToken.Null ? null : reader.Value?.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str) || !DateOnly.TryParse(str, out var date))
|
||||
{
|
||||
return objectType == typeof(DateOnly?) ? null : default(DateOnly);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
#region 废弃
|
||||
|
||||
|
||||
@@ -49,8 +49,6 @@ namespace IRaCIS.Core.API
|
||||
//必须放在后面
|
||||
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
|
||||
|
||||
//options.SerializerSettings.Converters.Add(new DateOnlyUniversalJsonConverter("yyyy-MM-dd"));
|
||||
|
||||
|
||||
})
|
||||
.AddControllersAsServices()//动态webApi属性注入需要
|
||||
|
||||
@@ -3,10 +3,8 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
@@ -53,90 +51,34 @@ namespace IRaCIS.Core.API
|
||||
}
|
||||
public class NullToEmptyStringValueProvider : IValueProvider
|
||||
{
|
||||
PropertyInfo _memberInfo;
|
||||
private readonly bool _isNullable;
|
||||
PropertyInfo _MemberInfo;
|
||||
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
|
||||
{
|
||||
_memberInfo = memberInfo;
|
||||
|
||||
_MemberInfo = memberInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断
|
||||
public object GetValue(object target)
|
||||
{
|
||||
var result = _memberInfo.GetValue(target);
|
||||
// 检查类型是否为string或string?
|
||||
if (_memberInfo.PropertyType == typeof(string) && result == null)
|
||||
{
|
||||
object result = _MemberInfo.GetValue(target);
|
||||
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
|
||||
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
|
||||
|
||||
#region 返回的时候处理 string string? null 为"" 不区分处理
|
||||
|
||||
|
||||
//// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的
|
||||
|
||||
//var isNullable1 = _memberInfo.CustomAttributes
|
||||
// .Any(a => a.AttributeType.Name == "NullableAttribute");
|
||||
|
||||
////var isNullable2 = _memberInfo.CustomAttributes
|
||||
//// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
|
||||
//if (isNullable1)
|
||||
//{
|
||||
// return result;
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
// 如果是string类型,返回空字符串
|
||||
return string.Empty;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化
|
||||
public void SetValue(object target, object value)
|
||||
{
|
||||
|
||||
_memberInfo.SetValue(target, value);
|
||||
|
||||
#region 前端针对 string 类型的变量,如果传递null 会报错必传
|
||||
|
||||
//if (_memberInfo.PropertyType == typeof(string))
|
||||
//{
|
||||
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// _memberInfo.SetValue(target, value);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 处理模型验证区分 string string?
|
||||
|
||||
//////接收模型的时候 定义的明明是string 但是上面也有该属性,判断不准的 比如阅片跟踪列表查询
|
||||
//var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute");
|
||||
|
||||
////不影响 string? 传递null 变为""
|
||||
//if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false)
|
||||
//{
|
||||
// //如果不处理 前段传递null string不会接收,说前段没传递会验证报错
|
||||
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
|
||||
// _memberInfo.SetValue(target, value);
|
||||
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
if (_MemberInfo.PropertyType == typeof(string))
|
||||
{
|
||||
//去掉前后空格
|
||||
_MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_MemberInfo.SetValue(target, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,20 +22,16 @@ namespace IRaCIS.Core.API
|
||||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("MassTransit", LogEventLevel.Warning)
|
||||
//https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Logging.md
|
||||
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
|
||||
|
||||
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.FromLogContext()
|
||||
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:365);
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
|
||||
|
||||
#region 根据环境配置是否打开错误发送邮件通知
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
@@ -35,8 +35,7 @@
|
||||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
@@ -56,15 +55,11 @@
|
||||
"ChangePassWordDays": 90,
|
||||
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
"TemplateType": 2
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "uat@extimaging.com",
|
||||
"FromName": "UAT_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
@@ -74,13 +69,7 @@
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,16 +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"
|
||||
},
|
||||
"WeComNoticeConfig": {
|
||||
"IsOpenWeComNotice": true,
|
||||
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
|
||||
"APINoticeUserList": [ "u", "wait..." ],
|
||||
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
|
||||
//"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",
|
||||
@@ -42,8 +36,7 @@
|
||||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
@@ -60,45 +53,30 @@
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
"TemplateType": 2
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "irc@extimaging.com",
|
||||
"FromName": "IRC Imaging System",
|
||||
"FromName": "irc",
|
||||
"AuthorizationCode": "ExtImg@2022",
|
||||
"SiteUrl": "http://irc.extimaging.com/login",
|
||||
|
||||
"PlatformName": "EICS",
|
||||
"PlatformNameCN": "展影云平台",
|
||||
"SystemShortName": "IRC",
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"CronEmailDefaultCulture": "zh-CN",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "101.132.193.237"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,193 +1,104 @@
|
||||
{
|
||||
// 日志信息
|
||||
"Logging": {
|
||||
// 日志等级
|
||||
"LogLevel": {
|
||||
// 默认日志等级
|
||||
"Default": "Information",
|
||||
// 调试日志等级
|
||||
"Microsoft": "Warning",
|
||||
// ASP.NET Core 日志等级
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
// 数据库链接字符串
|
||||
"ConnectionStrings": {
|
||||
// 本地数据库链接字符串
|
||||
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
// Hangfire 定时任务数据库链接字符串
|
||||
"Hangfire": "Server=106.14.89.110,1435;Database=Test_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"WeComNoticeConfig": {
|
||||
"IsOpenWeComNotice": true,
|
||||
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
|
||||
"APINoticeUserList": [ "u", "wait..." ],
|
||||
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
|
||||
},
|
||||
// 对象存储服务配置
|
||||
|
||||
"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": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
// 打开用户复杂密码
|
||||
|
||||
"OpenUserComplexPassword": false,
|
||||
// 是否在开始工作前强制签署电子知情同意书
|
||||
"OpenSignDocumentBeforeWork": false,
|
||||
// 是否启用登录失败次数限制(防暴力破解)
|
||||
|
||||
"OpenLoginLimit": false,
|
||||
// 连续登录失败多少次后触发锁定
|
||||
"LoginMaxFailCount": 5,
|
||||
// 触发锁定后账号锁定时长(分钟)
|
||||
"LoginFailLockMinutes": 1,
|
||||
// 无操作自动登出的超时时间(分钟)
|
||||
"AutoLoginOutMinutes": 10,
|
||||
// 是否启用多因子登录认证(MFA)
|
||||
|
||||
"OpenLoginMFA": false,
|
||||
// 连续阅片的最长工作时间(分钟),超时后强制休息
|
||||
|
||||
"ContinuousReadingTimeMin": 120,
|
||||
// 强制休息时长(分钟)
|
||||
"ReadingRestTimeMin": 10,
|
||||
// 是否强制用户定期修改密码
|
||||
|
||||
"IsNeedChangePassWord": true,
|
||||
// 密码有效期(天),到期后必须修改
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate(LiLi) 2 Extensive(展影)
|
||||
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
// 是否打开项目关联删除
|
||||
|
||||
"OpenTrialRelationDelete": true,
|
||||
// 转换PDF服务配置
|
||||
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf",
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
|
||||
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
|
||||
},
|
||||
// 邮件服务配置(用于系统通知、找回密码、错误报警等)
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
// SMTP端口
|
||||
"Port": 465,
|
||||
// 企业邮箱SMTP服务器地址
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
|
||||
"ImapPort": 993,
|
||||
// 发件人邮箱地址
|
||||
"FromEmail": "test@extimaging.com",
|
||||
// 发件人显示名称
|
||||
"FromName": "Test IRC Imaging System",
|
||||
// SMTP授权码
|
||||
"FromName": "Test_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
// 系统对外访问地址
|
||||
"SiteUrl": "http://irc.test.extimaging.com/login",
|
||||
|
||||
"PlatformName": "EICS",
|
||||
"PlatformNameCN": "展影云平台",
|
||||
// 系统简称一致性核查使用
|
||||
"SystemShortName": "IRC",
|
||||
// 组织英文名称
|
||||
"OrganizationName": "ExtImaging",
|
||||
// 组织中文名称
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
// 公司英文全称
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
// 公司中文全称
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
// 公司英文简称
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
// 公司中文简称
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
// 是否为国际版环境
|
||||
"IsEnv_US": false,
|
||||
// 是否开启系统异常邮件报警
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
// 邮箱格式校验正则表达式
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"CronEmailDefaultCulture": "zh-CN",
|
||||
// 接收系统异常报警的邮箱列表
|
||||
"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": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": false,
|
||||
|
||||
"OpenSignDocumentBeforeWork": false,
|
||||
@@ -76,15 +75,13 @@
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "test@extimaging.com",
|
||||
"FromName": "Test_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
"SiteUrl": "http://irc.test.extimaging.com/login",
|
||||
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
@@ -94,12 +91,5 @@
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "106.14.89.110"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,7 @@
|
||||
//"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
//"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
},
|
||||
"WeComNoticeConfig": {
|
||||
"IsOpenWeComNotice": true,
|
||||
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
|
||||
"APINoticeUserList": [ "u", "wait..." ],
|
||||
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
|
||||
},
|
||||
|
||||
"ObjectStoreService": {
|
||||
"ObjectStoreUse": "AWS",
|
||||
"MinIO": {
|
||||
@@ -43,8 +38,7 @@
|
||||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
@@ -63,24 +57,17 @@
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
|
||||
"OpenTrialRelationDelete": false,
|
||||
//MFA免验证
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
"OpenTrialRelationDelete": false
|
||||
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi System",
|
||||
"FromName": "LiLi",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
|
||||
"PlatformName": "LiLi",
|
||||
"PlatformNameCN": "LiLi",
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
@@ -91,21 +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,}$",
|
||||
"CronEmailDefaultCulture": "en-US",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "44.210.231.169"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
@@ -67,24 +66,16 @@
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
"OpenLoginMFA": true,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
|
||||
"OpenLoginMFA": true
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi System",
|
||||
"FromName": "LiLi",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
|
||||
"PlatformName": "LiLi",
|
||||
"PlatformNameCN": "LiLi",
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
@@ -101,13 +92,6 @@
|
||||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "3.226.182.187"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
"RemoteNew": "Server=3.226.182.187,1435;Database=US_Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=3.226.182.187,1435;Database=US_Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"WeComNoticeConfig": {
|
||||
"IsOpenWeComNotice": true,
|
||||
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
|
||||
"APINoticeUserList": [ "u", "wait..." ],
|
||||
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
|
||||
},
|
||||
|
||||
"ObjectStoreService": {
|
||||
|
||||
@@ -54,8 +48,7 @@
|
||||
}
|
||||
},
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
@@ -72,22 +65,16 @@
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
"TemplateType": 1
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 587,
|
||||
"Host": "smtp-mail.outlook.com",
|
||||
"Imap": "imap-mail.outlook.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "donotreply@elevateimaging.ai",
|
||||
"FromName": "LiLi System",
|
||||
"FromName": "LiLi",
|
||||
"AuthorizationCode": "Q#669869497420ul",
|
||||
|
||||
"PlatformName": "LiLi",
|
||||
"PlatformNameCN": "LiLi",
|
||||
"SystemShortName": "LiLi",
|
||||
"OrganizationName": "Elevate Imaging",
|
||||
"OrganizationNameCN": "Elevate Imaging",
|
||||
@@ -98,20 +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,}$",
|
||||
"CronEmailDefaultCulture": "en-US",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "104",
|
||||
"IP": "3.226.182.187"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"WeComNoticeConfig": {
|
||||
"IsOpenWeComNotice": true,
|
||||
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
|
||||
"APINoticeUserList": [ "u", "wait..." ],
|
||||
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621697b96f74e6f3d" ]
|
||||
},
|
||||
"ObjectStoreService": {
|
||||
|
||||
"ObjectStoreUse": "AliyunOSS",
|
||||
@@ -60,8 +54,7 @@
|
||||
},
|
||||
|
||||
"BasicSystemConfig": {
|
||||
// 启用质控风险控制功能
|
||||
"QCRiskControl": true,
|
||||
|
||||
"OpenUserComplexPassword": true,
|
||||
"OpenSignDocumentBeforeWork": true,
|
||||
|
||||
@@ -79,47 +72,32 @@
|
||||
"IsNeedChangePassWord": true,
|
||||
"ChangePassWordDays": 90,
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
"TemplateType": 2
|
||||
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
"Host": "smtp.qiye.aliyun.com",
|
||||
"Imap": "imap.qiye.aliyun.com",
|
||||
"ImapPort": 993,
|
||||
"FromEmail": "uat@extimaging.com",
|
||||
"FromName": "Uat IRC Imaging System",
|
||||
"FromName": "UAT_IRC",
|
||||
"AuthorizationCode": "SHzyyl2021",
|
||||
"SiteUrl": "http://irc.uat.extimaging.com/login",
|
||||
|
||||
"PlatformName": "EICS",
|
||||
"PlatformNameCN": "展影云平台",
|
||||
"SystemShortName": "IRC",
|
||||
"OrganizationName": "ExtImaging",
|
||||
"OrganizationNameCN": "ExtImaging",
|
||||
"OrganizationName": "Extlmaging",
|
||||
"OrganizationNameCN": "Extlmaging",
|
||||
"CompanyName": "Extensive Imaging",
|
||||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||
"CronEmailDefaultCulture": "zh-CN",
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
"IP": "101.132.253.119"
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
|
||||
"Issuer": "Extimaging",
|
||||
"Audience": "EICS",
|
||||
"TokenExpireMinute": "10080" //7天
|
||||
"TokenExpireMinute": "10080"//7天
|
||||
},
|
||||
"IpRateLimiting": {
|
||||
"EnableEndpointRateLimiting": true,
|
||||
@@ -74,12 +74,5 @@
|
||||
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
|
||||
"scope": "repo"
|
||||
}
|
||||
},
|
||||
"RequestDuplicationOptions": {
|
||||
"IsEnabled": true,
|
||||
"DuplicationWindowMs": 200,
|
||||
"CacheTimeSeconds": 5,
|
||||
"ExcludedPaths": [
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>%(DocumentTitle)</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
|
||||
@@ -505,8 +505,8 @@ var abp = abp || {};
|
||||
|
||||
//Inputs
|
||||
createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)');
|
||||
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'user1wj');
|
||||
createInput(modalUxContent, 'password', 'Password', 'password', '1');
|
||||
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'cyldev');
|
||||
createInput(modalUxContent, 'password', 'Password', 'password', '123456');
|
||||
createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', '');
|
||||
createSelect(modalUxContent, 'roleSelect', 'role', [])
|
||||
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
using IRaCIS.Core.Application.Helper.OtherTool;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
|
||||
|
||||
public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : ActionFilterAttribute, IActionFilter
|
||||
public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter
|
||||
{
|
||||
|
||||
|
||||
@@ -31,55 +23,8 @@ public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _conf
|
||||
.Select(e => e.ErrorMessage)
|
||||
.ToArray();
|
||||
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
try
|
||||
{
|
||||
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
|
||||
|
||||
if (isOpenWeComNotice)
|
||||
{
|
||||
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
|
||||
|
||||
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
|
||||
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
var userList = _config.GetSection("WeComNoticeConfig:VueNoticeUserList").Get<string[]>();
|
||||
|
||||
var requestBody = context.HttpContext.Items["RequestBody"] as string;
|
||||
|
||||
|
||||
// 🔔 异步告警(不要阻塞请求)
|
||||
_ = WeComNotifier.SendAlertAsync(
|
||||
webhook: webhook,
|
||||
alert: new WeComAlert
|
||||
{
|
||||
Env = baseUrl,
|
||||
UserName = _userInfo.UserName.IsNotNullOrEmpty()? $"{_userInfo.UserName}({_userInfo.UserTypeShortName})": "匿名",
|
||||
Api = $"{request.Method} {request.Path}",
|
||||
Message = $"前端传递参数,后端模型验证失败\n 具体信息:{JsonConvert.SerializeObject(validationErrors)}",
|
||||
JsonData = requestBody,
|
||||
AtUsers = userList ?? []
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"模型验证过滤器里发送企业微信出现错误:{ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---提供给接口的参数无效。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject(validationErrors)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using IRaCIS.Core.Application.Helper.OtherTool;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : Attribute, IExceptionFilter
|
||||
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
|
||||
{
|
||||
|
||||
public void OnException(ExceptionContext context)
|
||||
@@ -49,44 +46,7 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
|
||||
else
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
|
||||
: (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
try
|
||||
{
|
||||
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
|
||||
|
||||
if (isOpenWeComNotice)
|
||||
{
|
||||
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
|
||||
|
||||
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
|
||||
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
|
||||
|
||||
var requestBody = context.HttpContext.Items["RequestBody"] as string;
|
||||
|
||||
var alert = new WeComAlert
|
||||
{
|
||||
Env = baseUrl,
|
||||
UserName = $"{_userInfo.UserName}({_userInfo.UserTypeShortName})",
|
||||
Api = context.HttpContext.Request.Path,
|
||||
Message = exception.Message,
|
||||
JsonData = requestBody,
|
||||
Stack = exception.StackTrace,
|
||||
AtUsers = userList ?? []
|
||||
};
|
||||
|
||||
_ = WeComNotifier.SendAlertAsync(webhook, alert);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}");
|
||||
}
|
||||
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +58,6 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
|
||||
|
||||
_logger.LogError(errorInfo);
|
||||
|
||||
|
||||
//_logger.LogError(exception, exception.Message);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,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;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace IRaCIS.Core.Domain.Share;
|
||||
[Description("多环境 配置环境实体")]
|
||||
public class ServiceVerifyConfigOption
|
||||
{
|
||||
public bool QCRiskControl { get; set; }
|
||||
public bool OpenUserComplexPassword { get; set; }
|
||||
|
||||
public bool OpenSignDocumentBeforeWork { get; set; }
|
||||
@@ -39,8 +38,6 @@ public class ServiceVerifyConfigOption
|
||||
|
||||
public string ThirdPdfUrl { get; set; }
|
||||
|
||||
public int UserMFAVerifyMinutes { get; set; } = 1440;
|
||||
|
||||
}
|
||||
|
||||
public class SystemEmailSendConfig
|
||||
@@ -48,10 +45,6 @@ public class SystemEmailSendConfig
|
||||
public int Port { get; set; }
|
||||
|
||||
public string Host { get; set; } = string.Empty;
|
||||
|
||||
public string Imap { get; set; } = string.Empty;
|
||||
|
||||
public int ImapPort { get; set; }
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
|
||||
public string FromName { get; set; } = string.Empty;
|
||||
@@ -60,11 +53,7 @@ public class SystemEmailSendConfig
|
||||
|
||||
public string SiteUrl { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public string PlatformName { get; set; } = string.Empty;
|
||||
public string PlatformNameCN { get; set; } = string.Empty;
|
||||
|
||||
public string SystemShortName { get; set; } = string.Empty;
|
||||
public string SystemShortName { get; set; } = string.Empty;
|
||||
|
||||
public string OrganizationName { get; set; } = string.Empty;
|
||||
public string OrganizationNameCN { get; set; } = string.Empty;
|
||||
@@ -81,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;
|
||||
@@ -96,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
|
||||
@@ -116,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; }
|
||||
|
||||
@@ -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,11 +61,6 @@ public static class CacheKeys
|
||||
/// <returns></returns>
|
||||
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
|
||||
|
||||
//每个用户 每个浏览器独立时间
|
||||
public static string UserMFAVerifyPass(Guid userId,string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
|
||||
|
||||
public static string UserMFATag(Guid userId) => $"UserMFAVerifyPass:{userId}";
|
||||
|
||||
}
|
||||
|
||||
public static class CacheHelper
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
using DocumentFormat.OpenXml.Office.CustomUI;
|
||||
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 SubjectId { get; set; }
|
||||
public bool IsTaskStudy { get; set; }
|
||||
|
||||
public Guid SubjectVisitId { get; set; }
|
||||
// Study
|
||||
public Guid DicomStudyId { get; set; }
|
||||
|
||||
public string PatientId { get; set; }
|
||||
public string PatientName { get; set; }
|
||||
public string PatientBirthDate { get; set; }
|
||||
public string PatientSex { get; set; }
|
||||
|
||||
public string StudyInstanceUid { get; set; }
|
||||
public string StudyId { get; set; }
|
||||
public string DicomStudyDate { get; set; }
|
||||
public string DicomStudyTime { get; set; }
|
||||
public string AccessionNumber { get; set; }
|
||||
public string StudyDescription { get; set; }
|
||||
|
||||
// Series
|
||||
public string SeriesInstanceUid { get; set; }
|
||||
public string Modality { get; set; }
|
||||
public string DicomSeriesDate { get; set; }
|
||||
public string DicomSeriesTime { get; set; }
|
||||
public int SeriesNumber { get; set; }
|
||||
public string SeriesDescription { get; set; }
|
||||
|
||||
// Instance
|
||||
public Guid InstanceId { get; set; }
|
||||
public string SopInstanceUid { get; set; }
|
||||
public string SOPClassUID { get; set; }
|
||||
public int InstanceNumber { get; set; }
|
||||
public string MediaStorageSOPClassUID { get; set; }
|
||||
public string MediaStorageSOPInstanceUID { get; set; }
|
||||
public string TransferSytaxUID { get; set; }
|
||||
}
|
||||
public static class DicomDIRHelper
|
||||
{
|
||||
|
||||
public static async Task GenerateStudyDIRAndUploadAsync(List<StudyDIRInfo> list, Dictionary<string, string> dic, string ossFolder, IOSSService _oSSService)
|
||||
{
|
||||
var mappings = new List<string>();
|
||||
int index = 1;
|
||||
|
||||
var studyUid=list.FirstOrDefault()?.StudyInstanceUid;
|
||||
|
||||
var dicomDir = new DicomDirectory();
|
||||
|
||||
foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber))
|
||||
{
|
||||
var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID);
|
||||
|
||||
if (dicomUid != null)
|
||||
{
|
||||
var ts = DicomTransferSyntax.Query(dicomUid);
|
||||
|
||||
var dataset = new DicomDataset(ts)
|
||||
{
|
||||
{ DicomTag.PatientID, item.PatientId ?? string.Empty },
|
||||
{ DicomTag.PatientName, item.PatientName ?? string.Empty },
|
||||
{ DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
|
||||
{ DicomTag.PatientSex, item.PatientSex ?? string.Empty },
|
||||
|
||||
{ DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty },
|
||||
{ DicomTag.StudyID, item.StudyId ?? string.Empty },
|
||||
{ DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty },
|
||||
{ DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty },
|
||||
{ DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty },
|
||||
{ DicomTag.StudyDescription, item.StudyDescription ?? string.Empty },
|
||||
|
||||
{ DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty },
|
||||
{ DicomTag.Modality, item.Modality ?? string.Empty },
|
||||
{ DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty },
|
||||
{ DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty },
|
||||
{ DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty },
|
||||
{ DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty },
|
||||
|
||||
{ DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty },
|
||||
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty },
|
||||
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty },
|
||||
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty },
|
||||
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty },
|
||||
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty },
|
||||
};
|
||||
|
||||
var dicomFile = new DicomFile(dataset);
|
||||
|
||||
// 文件名递增格式:IM_00001, IM_00002, ...
|
||||
string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位
|
||||
|
||||
mappings.Add($"{filename} => {item.InstanceId}");
|
||||
|
||||
dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last());
|
||||
|
||||
dicomDir.AddFile(dicomFile, filename);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//有实际的文件
|
||||
if (mappings.Count > 0)
|
||||
{
|
||||
#region 写入临时路径
|
||||
var tempFilePath = Path.GetTempFileName();
|
||||
|
||||
// 保存 DICOMDIR 到临时文件 不能直接写入到流种
|
||||
await dicomDir.SaveAsync(tempFilePath);
|
||||
|
||||
using (var memoryStream = new MemoryStream(File.ReadAllBytes(tempFilePath)))
|
||||
{
|
||||
// 重置流位置
|
||||
memoryStream.Position = 0;
|
||||
|
||||
var relativePath= await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true);
|
||||
|
||||
dic.Add($"{studyUid}_DICOMDIR" , relativePath.Split('/').Last());
|
||||
}
|
||||
|
||||
//清理临时文件
|
||||
File.Delete(tempFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 映射上传
|
||||
|
||||
// 将映射写入内存流
|
||||
var mappingText = string.Join(Environment.NewLine, mappings);
|
||||
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
|
||||
|
||||
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false);
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using MailKit;
|
||||
using MailKit.Security;
|
||||
using MimeKit;
|
||||
using Org.BouncyCastle.Tls;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
@@ -10,15 +9,12 @@ namespace IRaCIS.Core.Application.Helper;
|
||||
public static class SendEmailHelper
|
||||
{
|
||||
|
||||
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
public static async Task SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
string result = string.Empty;
|
||||
result = messageToSend.MessageId;
|
||||
|
||||
//没有收件人 那么不发送
|
||||
if (messageToSend.To.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -46,7 +42,6 @@ public static class SendEmailHelper
|
||||
|
||||
await smtp.DisconnectAsync(true);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -56,76 +51,7 @@ public static class SendEmailHelper
|
||||
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, Trial trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
|
||||
// 项目的需要重设 发件地址与邮件地址
|
||||
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
|
||||
if (fromAddress != null)
|
||||
{
|
||||
messageToSend.From.Clear();
|
||||
messageToSend.From.Add(new MailboxAddress(trial.EmailFromName, trial.EmailFromEmail));
|
||||
}
|
||||
|
||||
|
||||
string result = string.Empty;
|
||||
result = messageToSend.MessageId;
|
||||
|
||||
//没有收件人 那么不发送
|
||||
if (messageToSend.To.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
|
||||
{
|
||||
if (messageSentSuccess != null)
|
||||
{
|
||||
smtp.MessageSent += messageSentSuccess;
|
||||
}
|
||||
|
||||
|
||||
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
|
||||
//await smtp.ConnectAsync("smtp.qq.com", 465, SecureSocketOptions.SslOnConnect);
|
||||
|
||||
//await smtp.AuthenticateAsync("zhou941003@qq.com", "sqfhlpfdvnexbcab");
|
||||
|
||||
|
||||
//await smtp.ConnectAsync(_systemEmailConfig.Host, _systemEmailConfig.Port, SecureSocketOptions.Auto);
|
||||
|
||||
//await smtp.AuthenticateAsync(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
|
||||
|
||||
|
||||
await smtp.ConnectAsync(trial.EmailSMTPServerAddress, trial.EmailSMTPServerPort, SecureSocketOptions.Auto);
|
||||
|
||||
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
|
||||
|
||||
|
||||
await smtp.SendAsync(messageToSend);
|
||||
|
||||
await smtp.DisconnectAsync(true);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
|
||||
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
|
||||
@@ -147,7 +73,7 @@ public static class SendEmailHelper
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig,Trial? trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
|
||||
{
|
||||
var messageToSend = new MimeMessage();
|
||||
|
||||
@@ -217,7 +143,7 @@ public static class SendEmailHelper
|
||||
|
||||
await smtp.ConnectAsync(sMTPEmailConfig.Host, sMTPEmailConfig.Port, SecureSocketOptions.Auto);
|
||||
|
||||
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
|
||||
await smtp.AuthenticateAsync(sMTPEmailConfig.UserName, sMTPEmailConfig.AuthorizationCode);
|
||||
|
||||
await smtp.SendAsync(messageToSend);
|
||||
|
||||
|
||||
-12
@@ -60,16 +60,4 @@ namespace IRaCIS.Core.Application.Helper
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public class DateTimeTranaslateAttribute : Attribute
|
||||
{
|
||||
public string Formart { get; set; }
|
||||
|
||||
public DateTimeTranaslateAttribute(string formart)
|
||||
{
|
||||
Formart = formart;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using FellowOakDicom.Imaging.LUT;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore.Migrations;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MiniExcelLibs;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using SharpCompress.Common;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using Xceed.Document.NET;
|
||||
@@ -248,7 +243,6 @@ public static class ExcelExportHelper
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class DynamicColumnConfig
|
||||
{
|
||||
/// <summary>
|
||||
@@ -266,9 +260,7 @@ public static class ExcelExportHelper
|
||||
/// </summary>
|
||||
public int TempalteLastColumnIndex { get; set; }
|
||||
|
||||
public bool IsCDISCExport { get; set; } = false;
|
||||
|
||||
//public List<string> CDISCList { get; set; } = new List<string>();
|
||||
public List<string> CDISCList { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 动态的列名 如果Id 重复,那么就按照名称填充,否则就按照Id 填充列数据
|
||||
@@ -309,8 +301,6 @@ public static class ExcelExportHelper
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string CDISCCode { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is not ColumItem other) return false;
|
||||
@@ -499,9 +489,7 @@ public static class ExcelExportHelper
|
||||
|
||||
if (dynamicColumnConfig != null)
|
||||
{
|
||||
//var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
|
||||
|
||||
var isCdics = dynamicColumnConfig.IsCDISCExport;
|
||||
var isCdics = dynamicColumnConfig.CDISCList.Count > 0;
|
||||
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
@@ -556,9 +544,9 @@ public static class ExcelExportHelper
|
||||
}
|
||||
|
||||
//创建新的列
|
||||
for (int i = originRemoveEndIndex; i < originRemoveEndIndex + needAddCount; i++)
|
||||
for (int i = originTotalEndIndex; i < originTotalEndIndex + needAddCount; i++)
|
||||
{
|
||||
|
||||
|
||||
titelRow.CreateCell(i + 1);
|
||||
templateRow.CreateCell(i + 1);
|
||||
|
||||
@@ -577,11 +565,6 @@ public static class ExcelExportHelper
|
||||
titelRow.GetCell(i).SetCellValue(titelRow.GetCell(i - gap).StringCellValue);
|
||||
|
||||
templateRow.GetCell(i).SetCellValue(templateRow.GetCell(i - gap).StringCellValue);
|
||||
|
||||
if (isCdics)
|
||||
{
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsRow.GetCell(i - gap).StringCellValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -590,7 +573,6 @@ public static class ExcelExportHelper
|
||||
|
||||
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
|
||||
{
|
||||
|
||||
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
|
||||
|
||||
titelRow.GetCell(i).SetCellValue(name);
|
||||
@@ -598,15 +580,13 @@ public static class ExcelExportHelper
|
||||
|
||||
if (isCdics)
|
||||
{
|
||||
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
|
||||
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
|
||||
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
using (var memoryStream2 = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream2, true);
|
||||
@@ -629,11 +609,8 @@ public static class ExcelExportHelper
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
//await MiniExcel.SaveAsByTemplateAsync("testmini.xlsx", templateStream.ToArray(), translateData);
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (dynamicColumnConfig != null)
|
||||
@@ -666,7 +643,7 @@ public static class ExcelExportHelper
|
||||
|
||||
var iteObjDic = itemObj.ToDictionary();
|
||||
|
||||
var itemDicName = iteObjDic.ContainsKey(dynamicColumnConfig.DynamicItemDicName) ? iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString() : "";
|
||||
var itemDicName = iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString();
|
||||
var itemValue = iteObjDic[dynamicColumnConfig.DynamicItemValueName]?.ToString();
|
||||
|
||||
//var writeIndex = itemList.IndexOf(itemObj) + dynamicColumnConfig.AutoColumnStartIndex;
|
||||
@@ -686,61 +663,13 @@ public static class ExcelExportHelper
|
||||
if (itemDicName.IsNotNullOrEmpty())
|
||||
{
|
||||
|
||||
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
|
||||
|
||||
var translatedItemData = "";
|
||||
//多选
|
||||
if (optionTypeEnumStr == "1")
|
||||
{
|
||||
int[] enumValues = new int[0];
|
||||
// 1. 反序列化 JSON 数组 (字符串枚举)
|
||||
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
|
||||
{
|
||||
enumValues = new int[1] { int.Parse(itemValue) };
|
||||
}
|
||||
else
|
||||
{
|
||||
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue) ?? new int[0];
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 2. 翻译每一项并输出逗号拼接字符串
|
||||
translatedItemData = string.Join(",",
|
||||
enumValues.Select(code =>
|
||||
dynamicTranslateDataList[itemDicName]
|
||||
.FirstOrDefault(t =>
|
||||
string.Equals(code.ToString(), t.Code, StringComparison.OrdinalIgnoreCase)
|
||||
) is var r && r != null
|
||||
? (isEn_US ? r.Value : r.ValueCN)
|
||||
: string.Empty
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
|
||||
}
|
||||
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
|
||||
row.GetCell(writeIndex).SetCellValue(translatedItemData);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var unit = iteObjDic.ContainsKey("Unit") ? iteObjDic["Unit"]?.ToString() : null;
|
||||
|
||||
if (unit.IsNotNullOrEmpty() && unit == "9")
|
||||
{
|
||||
row.GetCell(writeIndex).SetCellValue(itemValue + "%");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
row.GetCell(writeIndex).SetCellValue(itemValue);
|
||||
|
||||
}
|
||||
|
||||
row.GetCell(writeIndex).SetCellValue(itemValue);
|
||||
|
||||
}
|
||||
|
||||
@@ -871,16 +800,17 @@ public static class ExcelExportHelper
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateFileStream);
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
@@ -977,7 +907,7 @@ public static class ExcelExportHelper
|
||||
{
|
||||
var name = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].Name;
|
||||
|
||||
var cdicsCode = dynamicColumnConfig.ColumnIdNameList[i - dynamicColunmStartIndex].CDISCCode;
|
||||
var cdicsCode = dynamicColumnConfig.CDISCList[i - dynamicColunmStartIndex];
|
||||
|
||||
cdicsRow.GetCell(i).SetCellValue(cdicsCode);
|
||||
titelRow.GetCell(i).SetCellValue(name);
|
||||
@@ -985,8 +915,14 @@ public static class ExcelExportHelper
|
||||
}
|
||||
}
|
||||
|
||||
workbook.Write(templateStream, leaveOpen: true);
|
||||
templateStream.Position = 0;
|
||||
using (var memoryStream2 = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1084,91 +1020,6 @@ public static class ExcelExportHelper
|
||||
|
||||
|
||||
|
||||
public static async Task<IActionResult> MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment)
|
||||
{
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
var templateStream = new MemoryStream();
|
||||
|
||||
|
||||
#region npoi 移除某一行
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
if (isEn_US)
|
||||
{
|
||||
// 打开模板文件
|
||||
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
using var workbook = new XSSFWorkbook(templateFileStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
int removeRowIndex = 1; // 要删除的行(0-based)
|
||||
|
||||
for (int i = 0; i < sheetCount; i++)
|
||||
{
|
||||
var sheet = workbook.GetSheetAt(i);
|
||||
|
||||
// 2️ 删除行
|
||||
var row = sheet.GetRow(removeRowIndex);
|
||||
if (row != null)
|
||||
{
|
||||
sheet.RemoveRow(row);
|
||||
}
|
||||
|
||||
// 3️ 上移后续行
|
||||
if (removeRowIndex < sheet.LastRowNum)
|
||||
{
|
||||
sheet.ShiftRows(
|
||||
removeRowIndex + 1,
|
||||
sheet.LastRowNum,
|
||||
-1,
|
||||
true, // copyRowHeight
|
||||
false // resetOriginalRowHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
workbook.Write(templateStream, leaveOpen: true);
|
||||
templateStream.Position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var fs = new FileStream(tplPath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
fs.CopyTo(templateStream);
|
||||
}
|
||||
|
||||
templateStream.Position = 0;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), data, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件模板
|
||||
/// </summary>
|
||||
@@ -1250,7 +1101,4 @@ public static class ExcelExportHelper
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -69,15 +69,6 @@ public static class FileStoreHelper
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
public static string GetDonwnloadImageFolder(IWebHostEnvironment _hostEnvironment)
|
||||
{
|
||||
var rootPath = GetIRaCISRootPath(_hostEnvironment);
|
||||
|
||||
var rootFolder = Path.Combine(rootPath, StaticData.Folder.DownloadIamgeFolder);
|
||||
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
//根据相对路径 获取具体文件物理地址
|
||||
public static string GetPhysicalFilePath(IWebHostEnvironment _hostEnvironment, string relativePath)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit.Mediator;
|
||||
using System.Globalization;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
@@ -58,29 +57,25 @@ namespace IRaCIS.Core.Application.Helper
|
||||
}
|
||||
|
||||
|
||||
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron,string defaultCulture="")
|
||||
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron)
|
||||
{
|
||||
if (defaultCulture == "")
|
||||
{
|
||||
defaultCulture = CultureInfo.CurrentCulture.Name;
|
||||
}
|
||||
|
||||
switch (businessScenario)
|
||||
{
|
||||
|
||||
case EmailBusinessScenario.QCTask:
|
||||
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId,CultureInfoName= defaultCulture }, default), emailCron);
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.CRCToQCQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.QCToCRCImageQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
//加急阅片 10分钟
|
||||
case EmailBusinessScenario.ExpeditedReading:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -88,25 +83,14 @@ namespace IRaCIS.Core.Application.Helper
|
||||
|
||||
}
|
||||
|
||||
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron, string defaultCulture="")
|
||||
public static void AddOrUpdateSystemCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
|
||||
{
|
||||
if (defaultCulture == "")
|
||||
{
|
||||
defaultCulture = CultureInfo.CurrentCulture.Name;
|
||||
}
|
||||
|
||||
switch (businessScenario)
|
||||
{
|
||||
|
||||
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
|
||||
|
||||
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
|
||||
|
||||
Console.WriteLine("更新项目到期job");
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -16,10 +16,8 @@ using Minio;
|
||||
using Minio.DataModel;
|
||||
using Minio.DataModel.Args;
|
||||
using Minio.Exceptions;
|
||||
using System.IO;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Web;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
@@ -113,8 +111,6 @@ public class AliyunOSSTempToken
|
||||
|
||||
public string PreviewEndpoint { get; set; }
|
||||
|
||||
public string DownloadEndPoint => EndPoint.Insert(EndPoint.IndexOf("//") + 2, BucketName + ".");
|
||||
|
||||
}
|
||||
|
||||
[LowerCamelCaseJson]
|
||||
@@ -127,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,24 +139,18 @@ public enum ObjectStoreUse
|
||||
|
||||
public interface IOSSService
|
||||
{
|
||||
public Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false);
|
||||
public Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
public Task DeleteFromPrefix(string prefix, bool isCache = false);
|
||||
|
||||
public Task DeleteObjects(List<string> objectKeys, bool isCache = false);
|
||||
public Task DeleteObjects(List<string> objectKeys);
|
||||
|
||||
List<string> GetRootFolderNames();
|
||||
|
||||
@@ -187,518 +177,7 @@ public class OSSService : IOSSService
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定前缀下的所有现有文件立即转为目标存储类型
|
||||
/// </summary>
|
||||
/// <param name="prefix">要转换的文件前缀,如 "project-a/logs/"</param>
|
||||
/// <param name="ruleId">规则ID,默认为"immediate-archive"</param>
|
||||
/// <param name="isDelete">默认是添加/更新 </param>
|
||||
public async Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false)
|
||||
{
|
||||
|
||||
BackBatchGetToken();
|
||||
|
||||
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
|
||||
{
|
||||
// 1. 先获取现有的所有生命周期规则(避免覆盖)
|
||||
var existingRules = new List<Aliyun.OSS.LifecycleRule>();
|
||||
try
|
||||
{
|
||||
var existingRuleList = _ossClient.GetBucketLifecycle(aliConfig.BucketName);
|
||||
if (existingRuleList != null)
|
||||
{
|
||||
existingRules.AddRange(existingRuleList);
|
||||
Console.WriteLine($"找到 {existingRules.Count} 条现有规则");
|
||||
}
|
||||
}
|
||||
catch (OssException ex) when (ex.ErrorCode == "NoSuchLifecycle")
|
||||
{
|
||||
// 如果没有生命周期规则,继续创建新规则
|
||||
Console.WriteLine("当前Bucket无生命周期规则,将创建新规则");
|
||||
}
|
||||
|
||||
// 2. 创建立即生效的转换规则
|
||||
|
||||
ruleId = $"{ruleId}_{prefix}";
|
||||
var immediateRule = new Aliyun.OSS.LifecycleRule
|
||||
{
|
||||
ID = ruleId,
|
||||
Prefix = prefix,
|
||||
Status = RuleStatus.Enabled,
|
||||
Transitions = new Aliyun.OSS.LifecycleRule.LifeCycleTransition[]
|
||||
{
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 1
|
||||
},
|
||||
StorageClass = StorageClass.IA
|
||||
},
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 30 //最后一次修改时间
|
||||
},
|
||||
StorageClass = StorageClass.Archive
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// 3. 移除同名的旧规则(如果存在)
|
||||
existingRules.RemoveAll(r => r.ID == ruleId);
|
||||
|
||||
// 4. 添加新规则到规则列表
|
||||
if (isDelete == false)
|
||||
{
|
||||
existingRules.Add(immediateRule);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
var request = new SetBucketLifecycleRequest(aliConfig.BucketName)
|
||||
{
|
||||
LifecycleRules = existingRules
|
||||
};
|
||||
|
||||
|
||||
_ossClient.SetBucketLifecycle(request);
|
||||
|
||||
|
||||
}
|
||||
catch (OssException ex)
|
||||
{
|
||||
Log.Logger.Error($"❌ 设置失败 [错误码: {ex.ErrorCode}] 详细: {ex.Message}");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"❌ 发生未知错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
// 1. 获取现有的生命周期配置(避免覆盖)
|
||||
LifecycleConfiguration existingConfig = null;
|
||||
|
||||
var getRequest = new GetLifecycleConfigurationRequest { BucketName = awsConfig.BucketName };
|
||||
var response = await amazonS3Client.GetLifecycleConfigurationAsync(getRequest);
|
||||
existingConfig = response.Configuration;
|
||||
Console.WriteLine($"找到 {existingConfig?.Rules?.Count ?? 0} 条现有规则");
|
||||
|
||||
// 2. 生成唯一的规则ID
|
||||
ruleId = $"{ruleId}_{prefix.Replace('/', '_').Trim('_')}";
|
||||
|
||||
// 3. 创建新的生命周期规则
|
||||
var immediateRule = new Amazon.S3.Model.LifecycleRule
|
||||
{
|
||||
Id = ruleId,
|
||||
Filter = new LifecycleFilter
|
||||
{
|
||||
// 使用前缀筛选对象
|
||||
LifecycleFilterPredicate = new LifecyclePrefixPredicate { Prefix = prefix }
|
||||
},
|
||||
Status = LifecycleRuleStatus.Enabled,
|
||||
// 定义多个转换阶段
|
||||
Transitions = new List<LifecycleTransition>
|
||||
{
|
||||
// 1天后转为低频访问 (Standard-IA)
|
||||
//new LifecycleTransition
|
||||
//{
|
||||
// Days = 1, //Days' in Transition action must be greater than or equal to 30 for storageClass 'STANDARD_IA'"
|
||||
// StorageClass = S3StorageClass.StandardInfrequentAccess // 对应S3 Standard-IA
|
||||
//},
|
||||
// 30天后转为归档 (Glacier Instant Retrieval)
|
||||
new LifecycleTransition
|
||||
{
|
||||
Days = 30, //创建时间
|
||||
StorageClass = S3StorageClass.GlacierInstantRetrieval // 对应归档(即时检索)
|
||||
}
|
||||
// 如果需要更深的归档,可以继续添加:
|
||||
// new LifecycleTransition { Days = 90, StorageClass = S3StorageClass.GlacierFlexibleRetrieval },
|
||||
// new LifecycleTransition { Days = 180, StorageClass = S3StorageClass.DeepArchive }
|
||||
}
|
||||
// 注意:S3的生命周期规则不支持设置“立即生效(Days=0)”。
|
||||
// 如果要对存量文件立即生效,需要配合其他方法(如批量修改存储类型)。
|
||||
};
|
||||
|
||||
// 4. 更新规则列表(移除同名旧规则,添加新规则)
|
||||
var existingRules = existingConfig.Rules ?? new List<Amazon.S3.Model.LifecycleRule>();
|
||||
existingRules.RemoveAll(r => r.Id == ruleId);
|
||||
|
||||
if (isDelete == false)
|
||||
{
|
||||
existingRules.Add(immediateRule);
|
||||
|
||||
}
|
||||
|
||||
// 5. 提交新的生命周期配置
|
||||
var putRequest = new PutLifecycleConfigurationRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Configuration = new LifecycleConfiguration { Rules = existingRules }
|
||||
};
|
||||
|
||||
await amazonS3Client.PutLifecycleConfigurationAsync(putRequest);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
/// 解冻指定前缀下的所有归档/冷归档文件
|
||||
/// </summary>
|
||||
/// <param name="prefix">要解冻的文件前缀</param>
|
||||
/// <param name="restoreDays">解冻后文件保持可读的天数(默认3天)</param>
|
||||
/// <param name="restoreTier">解冻优先级(仅AWS有效)</param>
|
||||
/// <param name="batchSize">批量处理大小(默认100)</param>
|
||||
public async Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100)
|
||||
{
|
||||
BackBatchGetToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
var client = new OssClient(
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||||
AliyunOSSTempToken.AccessKeyId,
|
||||
AliyunOSSTempToken.AccessKeySecret,
|
||||
AliyunOSSTempToken.SecurityToken
|
||||
);
|
||||
|
||||
var bucketName = aliConfig.BucketName;
|
||||
int totalRestored = 0;
|
||||
int totalSkipped = 0;
|
||||
int totalFailed = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"开始解冻阿里云OSS文件,前缀: {prefix}");
|
||||
|
||||
var allObjects = new List<OssObjectSummary>();
|
||||
|
||||
// 1. 分页列举文件
|
||||
string nextMarker = null;
|
||||
ObjectListing result = null;
|
||||
|
||||
do
|
||||
{
|
||||
var listRequest = new Aliyun.OSS.ListObjectsRequest(bucketName)
|
||||
{
|
||||
Prefix = prefix,
|
||||
Marker = nextMarker,
|
||||
MaxKeys = batchSize
|
||||
};
|
||||
|
||||
result = client.ListObjects(listRequest);
|
||||
|
||||
allObjects.AddRange(result.ObjectSummaries);
|
||||
|
||||
|
||||
|
||||
nextMarker = result.NextMarker;
|
||||
|
||||
} while (result.IsTruncated);
|
||||
|
||||
// 2️⃣ 并行解冻(控制并发)
|
||||
Parallel.ForEach(
|
||||
allObjects,
|
||||
new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
|
||||
},
|
||||
obj =>
|
||||
{
|
||||
// 只处理归档
|
||||
if (obj.StorageClass != StorageClass.Archive.ToString())
|
||||
{
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var restoreRequest = new Aliyun.OSS.RestoreObjectRequest(bucketName, obj.Key)
|
||||
{
|
||||
Days = restoreDays
|
||||
};
|
||||
|
||||
client.RestoreObject(restoreRequest);
|
||||
|
||||
Interlocked.Increment(ref totalRestored);
|
||||
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
|
||||
}
|
||||
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
|
||||
{
|
||||
// 已在解冻中,算成功
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref totalFailed);
|
||||
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 3. 输出统计结果
|
||||
Console.WriteLine("\n================ 解冻完成 ================");
|
||||
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
|
||||
Console.WriteLine($"成功解冻: {totalRestored} 个");
|
||||
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
|
||||
Console.WriteLine($"解冻失败: {totalFailed} 个");
|
||||
|
||||
if (totalRestored > 0)
|
||||
{
|
||||
Console.WriteLine($"\n📋 解冻说明:");
|
||||
Console.WriteLine($" • 解冻任务已提交,文件将在后台处理");
|
||||
Console.WriteLine($" • 解冻完成后,文件将保持可读状态 {restoreDays} 天");
|
||||
Console.WriteLine($" • 归档文件约需1分钟,冷归档需数小时");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"❌ 阿里云解冻操作失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
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.GetBySystemName(awsConfig.Region),
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
using var client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var bucketName = awsConfig.BucketName;
|
||||
int totalRestored = 0;
|
||||
int totalSkipped = 0;
|
||||
int totalFailed = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"开始解冻AWS S3文件,前缀: {prefix}");
|
||||
|
||||
var allObjects = new List<S3Object>();
|
||||
|
||||
// 1. 分页列举文件
|
||||
string continuationToken = null;
|
||||
ListObjectsV2Response response = null;
|
||||
|
||||
do
|
||||
{
|
||||
var listRequest = new ListObjectsV2Request
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Prefix = prefix,
|
||||
ContinuationToken = continuationToken,
|
||||
MaxKeys = batchSize
|
||||
};
|
||||
|
||||
response = await client.ListObjectsV2Async(listRequest);
|
||||
|
||||
allObjects.AddRange(response.S3Objects);
|
||||
|
||||
|
||||
continuationToken = response.NextContinuationToken;
|
||||
|
||||
} while (response.IsTruncated == true);
|
||||
|
||||
// 2️⃣ 并行解冻(控制并发)
|
||||
await Parallel.ForEachAsync(
|
||||
allObjects,
|
||||
new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
|
||||
},
|
||||
async (obj, ct) =>
|
||||
{
|
||||
// 只处理归档
|
||||
if (obj.StorageClass != S3StorageClass.Glacier)
|
||||
{
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var restoreRequest = new Amazon.S3.Model.RestoreObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = obj.Key,
|
||||
Days = restoreDays,
|
||||
|
||||
};
|
||||
|
||||
await client.RestoreObjectAsync(restoreRequest);
|
||||
|
||||
Interlocked.Increment(ref totalRestored);
|
||||
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
|
||||
}
|
||||
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
|
||||
{
|
||||
// 已在解冻中,算成功
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref totalFailed);
|
||||
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 3. 输出统计结果
|
||||
Console.WriteLine("\n================ 解冻完成 ================");
|
||||
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
|
||||
Console.WriteLine($"成功解冻: {totalRestored} 个");
|
||||
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
|
||||
Console.WriteLine($"解冻失败: {totalFailed} 个");
|
||||
|
||||
if (totalRestored > 0)
|
||||
{
|
||||
Console.WriteLine($"\n📋 AWS解冻说明:");
|
||||
Console.WriteLine($" • 解冻任务已提交到Glacier服务");
|
||||
Console.WriteLine($" • 标准解冻: 3-5小时 (Glacier Flexible Retrieval)");
|
||||
Console.WriteLine($" • 加急解冻: 1-5分钟 (额外收费)");
|
||||
Console.WriteLine($" • 解冻后文件可读 {restoreDays} 天");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"❌ AWS解冻操作失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 坑方法,会清空之前的规则
|
||||
/// </summary>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="ruleId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public async Task SetLifecycle(string prefix, string ruleId = "immediate-archive")
|
||||
{
|
||||
BackBatchGetToken();
|
||||
|
||||
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);
|
||||
|
||||
ruleId = $"{ruleId}_{prefix}";
|
||||
var rule = new Aliyun.OSS.LifecycleRule
|
||||
{
|
||||
ID = ruleId,
|
||||
Prefix = prefix,
|
||||
Status = RuleStatus.Enabled,
|
||||
Transitions = new Aliyun.OSS.LifecycleRule.LifeCycleTransition[]
|
||||
{
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 1
|
||||
},
|
||||
StorageClass = StorageClass.IA
|
||||
},
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 30
|
||||
},
|
||||
StorageClass = StorageClass.Archive
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//会清空之前历史的规则,不能用。。。
|
||||
var request = new SetBucketLifecycleRequest(aliConfig.BucketName);
|
||||
request.AddLifecycleRule(rule);
|
||||
|
||||
_ossClient.SetBucketLifecycle(request);
|
||||
|
||||
|
||||
}
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||
@@ -710,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}";
|
||||
|
||||
@@ -765,8 +244,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -800,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
|
||||
@@ -838,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")
|
||||
{
|
||||
@@ -891,8 +333,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -918,7 +360,7 @@ public class OSSService : IOSSService
|
||||
|
||||
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
|
||||
{
|
||||
BackBatchGetToken();
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||
try
|
||||
@@ -931,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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -966,8 +410,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -1000,116 +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);
|
||||
|
||||
// 直接返回流
|
||||
return result.Content;
|
||||
|
||||
//// 将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 pipe = new System.IO.Pipelines.Pipe();
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var args = new GetObjectArgs()
|
||||
.WithBucket(minIOConfig.BucketName)
|
||||
.WithObject(ossRelativePath)
|
||||
.WithCallbackStream(stream =>
|
||||
{
|
||||
stream.CopyTo(pipe.Writer.AsStream());
|
||||
});
|
||||
|
||||
await minioClient.GetObjectAsync(args);
|
||||
await pipe.Writer.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await pipe.Writer.CompleteAsync(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return pipe.Reader.AsStream();
|
||||
}
|
||||
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);
|
||||
|
||||
// ⭐ 直接返回流
|
||||
return response.ResponseStream;
|
||||
|
||||
//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();
|
||||
@@ -1172,8 +506,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -1434,7 +768,7 @@ public class OSSService : IOSSService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除某个目录的文件 (包含单个文件,oss单个文件需要去除前缀/)
|
||||
/// 删除某个目录的文件
|
||||
/// </summary>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
@@ -1546,8 +880,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -1561,7 +895,7 @@ public class OSSService : IOSSService
|
||||
|
||||
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
|
||||
|
||||
if (listObjectsResponse.S3Objects?.Count > 0)
|
||||
if (listObjectsResponse.S3Objects.Count > 0)
|
||||
{
|
||||
// 准备删除请求
|
||||
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
|
||||
@@ -1591,7 +925,7 @@ public class OSSService : IOSSService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteObjects(List<string> objectKeys, bool isCache = false)
|
||||
public async Task DeleteObjects(List<string> objectKeys)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
@@ -1601,23 +935,9 @@ public class OSSService : IOSSService
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
var bucketName = string.Empty;
|
||||
|
||||
if (isCache)
|
||||
{
|
||||
Uri uri = new Uri(aliConfig.ViewEndpoint);
|
||||
string host = uri.Host; // 获取 "zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com"
|
||||
string[] parts = host.Split('.');
|
||||
bucketName = parts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketName = aliConfig.BucketName;
|
||||
}
|
||||
|
||||
if (objectKeys.Count > 0)
|
||||
{
|
||||
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(bucketName, objectKeys, false));
|
||||
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, objectKeys, false));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1653,8 +973,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
@@ -1680,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('/');
|
||||
@@ -1693,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;
|
||||
}
|
||||
@@ -1728,8 +1052,8 @@ public class OSSService : IOSSService
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
var request = new Amazon.S3.Model.GetObjectMetadataRequest
|
||||
@@ -1813,16 +1137,9 @@ public class OSSService : IOSSService
|
||||
{
|
||||
var awsOptions = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 创建 STS 客户端(考虑使用 RegionEndpoint)
|
||||
var stsConfig = new AmazonSecurityTokenServiceConfig
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsOptions.Region)
|
||||
};
|
||||
|
||||
//aws 临时凭证
|
||||
// 创建 STS 客户端
|
||||
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey, stsConfig);
|
||||
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
|
||||
|
||||
// 使用 AssumeRole 请求临时凭证
|
||||
var assumeRoleRequest = new AssumeRoleRequest
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityModel;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper.OtherTool;
|
||||
|
||||
|
||||
public class WeComAlert
|
||||
{
|
||||
public string Env { get; set; } = "";
|
||||
public string UserName { get; set; } = "";
|
||||
public string Api { get; set; } = "";
|
||||
public string Message { get; set; } = "";
|
||||
public string? JsonData { get; set; }
|
||||
public string? Stack { get; set; }
|
||||
public bool HasStack => !string.IsNullOrWhiteSpace(Stack);
|
||||
public string[] AtUsers { get; set; } = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class WeComNotifier
|
||||
{
|
||||
|
||||
public static async Task SendAlertAsync(string webhook, WeComAlert alert)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new RestClient();
|
||||
var request = new RestRequest(webhook, Method.Post);
|
||||
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 处理 @
|
||||
var atText = alert.AtUsers != null && alert.AtUsers.Any()
|
||||
? string.Join("", alert.AtUsers.Select(u => $" <@{u}>"))
|
||||
: "";
|
||||
|
||||
// 处理堆栈
|
||||
var stack = alert.Stack;
|
||||
if (!string.IsNullOrWhiteSpace(stack))
|
||||
{
|
||||
stack = stack.Replace("\n", "\n> ");
|
||||
if (stack.Length > 600)
|
||||
stack = stack[..600] + "...(已截断)";
|
||||
}
|
||||
|
||||
var markdown = $@"## 🚨 系统告警
|
||||
> {atText}
|
||||
> **部署环境:** [{alert.Env}]({alert.Env})
|
||||
> **发生时间:** {time}
|
||||
> **操作人:** {alert.UserName}
|
||||
> **接口地址:** {alert.Api}
|
||||
### ❗ 告警信息
|
||||
<font color=""warning"">{alert.Message}</font>";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(alert.JsonData))
|
||||
{
|
||||
markdown += $@"
|
||||
> **📦 请求数据(JSON 格式):**
|
||||
```json
|
||||
{alert.JsonData}
|
||||
```";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(stack))
|
||||
{
|
||||
markdown += $@"
|
||||
### 堆栈信息(部分)
|
||||
<font color=""comment"">{stack}</font>";
|
||||
}
|
||||
|
||||
var payload = new
|
||||
{
|
||||
msgtype = "markdown",
|
||||
markdown = new { content = markdown }
|
||||
};
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json);
|
||||
|
||||
await client.ExecuteAsync(request);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error("企业微信告警发送失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
public static class SafeBussinessHelper
|
||||
{
|
||||
public static async Task<bool> RunAsync(Func<Task> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
await func();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool Success, T? Result)> RunAsync<T>(Func<Task<T>> func, [CallerMemberName] string caller = "", string errorMsgTitle = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await func();
|
||||
return (true, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"【{errorMsgTitle}失败 - {caller}】: {ex.Message}");
|
||||
return (false, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,37 +33,38 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityModel.OidcClient" Version="6.0.0" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.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,24 +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; }
|
||||
|
||||
|
||||
[DictionaryTranslateAttribute("CheckState")]
|
||||
public CheckStateEnum CheckState { get; set; }
|
||||
}
|
||||
|
||||
|
||||
//[ExcelImporter(/*ImportResultFilter = typeof(ImportResultFilteTest),*/ IsLabelingError = true)]
|
||||
@@ -86,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
|
||||
@@ -82,7 +86,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
||||
|
||||
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
|
||||
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
|
||||
select new CheckDBModel()
|
||||
{
|
||||
SubjectVisitId = sv.Id,
|
||||
@@ -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,169 +301,12 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
|
||||
}
|
||||
await _subjectVisitRepository.SaveChangesAsync();
|
||||
|
||||
//await context.RespondAsync<ConsistenCheckResult>(new
|
||||
//{
|
||||
|
||||
//});
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<ConsistenFullCheckCommand> context)
|
||||
{
|
||||
var inspectionFileId = context.Message.InspectionFileId;
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
//系统定义MRI 到底是MR 还是MRI
|
||||
var mriModality = await _dictionaryRepository.Where(t => t.Parent.Code == "Modality").Where(t => t.Code == "MRI").Select(t => t.Value).FirstNotNullAsync();
|
||||
|
||||
//处理Excel大小写
|
||||
context.Message.ETCList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.StudyDate = Convert.ToDateTime(t.StudyDate).ToString("yyyy-MM-dd"); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
|
||||
var etcList = context.Message.ETCList;
|
||||
|
||||
|
||||
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId /*&& x.Subject.IsSubjectQuit == false*/ && x.AuditState == AuditStateEnum.QCPassed;
|
||||
|
||||
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
join study in _studyRepository.AsQueryable() on sv.Id equals study.SubjectVisitId
|
||||
select new CheckDBModel()
|
||||
{
|
||||
SubjectStatus = sv.Subject.Status,
|
||||
SubjectVisitId = sv.Id,
|
||||
SiteCode = sv.TrialSite.TrialSiteCode,
|
||||
StudyDate = study.StudyTime == null ? string.Empty : ((DateTime)study.StudyTime).ToString("yyyy-MM-dd"),
|
||||
StudyId = study.Id,
|
||||
Modality = study.ModalityForEdit,
|
||||
SubjectCode = subject.Code,
|
||||
VisitName = sv.VisitName,
|
||||
};
|
||||
|
||||
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
|
||||
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
|
||||
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
|
||||
select new CheckDBModel()
|
||||
{
|
||||
SubjectStatus = sv.Subject.Status,
|
||||
SubjectVisitId = sv.Id,
|
||||
SiteCode = sv.TrialSite.TrialSiteCode,
|
||||
StudyDate = noneDicomStudy.ImageDate.ToString("yyyy-MM-dd"),
|
||||
StudyId = noneDicomStudy.Id,
|
||||
Modality = noneDicomStudy.Modality,
|
||||
SubjectCode = subject.Code,
|
||||
VisitName = sv.VisitName,
|
||||
};
|
||||
|
||||
var dbList = (await dicomQuery.ToListAsync()).Union(await noneDicomQuey.ToListAsync()).ToList();
|
||||
|
||||
//处理数据库 大小写
|
||||
dbList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
|
||||
|
||||
var dbCheckList = _mapper.Map<List<CheckViewModel>>(dbList);
|
||||
|
||||
|
||||
//按照Excel数据访视分组 按照数据库的数据 一个个的访视对比
|
||||
var svExcelGroup = etcList.GroupBy(t => new { t.SiteCode, t.SubjectCode, t.VisitName })
|
||||
.Select(g => new { g.Key.SubjectCode, g.Key.VisitName, g.Key.SiteCode, ExcelStudyList = g.ToList() }).ToList();
|
||||
|
||||
var fullCheckResultList = new List<FullCheckResult>();
|
||||
|
||||
|
||||
foreach (var sv in svExcelGroup)
|
||||
{
|
||||
|
||||
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
|
||||
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
|
||||
{
|
||||
|
||||
var dbVisitStudyList = dbList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
|
||||
|
||||
//找到etc 当前visit site 和subject 一致的检查列表
|
||||
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
|
||||
|
||||
|
||||
//以我们系统数据库为准 ,判断是MR 还是MRI, 把Excel 里的MR 或者MRI 处理成一致,MR MRI 是一致的
|
||||
foreach (var item in etcVisitStudyList)
|
||||
{
|
||||
if (item.Modality == "MR" || item.Modality == "MRI")
|
||||
{
|
||||
item.Modality = mriModality;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//etc 和数据库 并集
|
||||
var unionList = dbVisitStudyList.Union(etcVisitStudyList);
|
||||
// 数据库存在
|
||||
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
|
||||
|
||||
// excel 存在
|
||||
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
|
||||
|
||||
var dbCurrentVisitFirst = dbVisitStudyList.First();
|
||||
|
||||
//ETC 和系统的完全一致 两者没有差别
|
||||
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
|
||||
{
|
||||
|
||||
|
||||
//每个受试者每个访视加一条记录
|
||||
fullCheckResultList.Add(new FullCheckResult()
|
||||
{
|
||||
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
|
||||
CheckTime = DateTime.Now,
|
||||
CheckState=CheckStateEnum.CVPassed,
|
||||
SiteCode = dbCurrentVisitFirst.SiteCode,
|
||||
SubjectCode = dbCurrentVisitFirst.SubjectCode,
|
||||
VisitName = dbCurrentVisitFirst.VisitName,
|
||||
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
|
||||
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? ""
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var checkResult =
|
||||
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality/*, _systemEmailConfig.SystemShortName*/]}")) + " | "
|
||||
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality, _systemEmailConfig.SystemShortName]}"));
|
||||
|
||||
//每个受试者每个访视加一条记录
|
||||
fullCheckResultList.Add(new FullCheckResult()
|
||||
{
|
||||
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
|
||||
CheckState = CheckStateEnum.None,
|
||||
CheckTime = DateTime.Now,
|
||||
SiteCode = dbCurrentVisitFirst.SiteCode,
|
||||
SubjectCode = dbCurrentVisitFirst.SubjectCode,
|
||||
VisitName = dbCurrentVisitFirst.VisitName,
|
||||
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
|
||||
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? "",
|
||||
CheckResult = checkResult
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//导到Excel 上传oss 回更记录状态
|
||||
|
||||
var list = fullCheckResultList;
|
||||
|
||||
var exportInfo = new ExcelExportInfo();
|
||||
|
||||
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
|
||||
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
|
||||
|
||||
var fileStreamResult = (FileStreamResult)await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialConsistentFUllCheckList_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(FullCheckResult));
|
||||
|
||||
var ossRelativePath = await _oSSService.UploadToOSSAsync(fileStreamResult.FileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", "DataReconciliation");
|
||||
|
||||
|
||||
//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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ public class ImageConsumer(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送影像重传相关邮件的通用方法 已经处理项目
|
||||
/// 发送影像重传相关邮件的通用方法
|
||||
/// </summary>
|
||||
private async Task SendImageReuploadEmail(SendImageReuploadEmailInDto inDto)
|
||||
{
|
||||
@@ -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)
|
||||
@@ -339,12 +353,12 @@ public class ImageConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
// 发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
// 添加抄送
|
||||
foreach (var ccUser in ccUserList)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
|
||||
}
|
||||
// 格式化邮件内容
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
@@ -365,11 +379,9 @@ public class ImageConsumer(
|
||||
return (topicStr, htmlBodyStr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +448,7 @@ public class ImageConsumer(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送阅片人筛选相关邮件的通用方法 已经处理项目
|
||||
/// 发送阅片人筛选相关邮件的通用方法
|
||||
/// </summary>
|
||||
private async Task SendReviewerSelectionEmail(EmailNoticeConfig emailNoticeConfig, Guid trialId, List<Guid> enrollIdList)
|
||||
{
|
||||
@@ -474,12 +486,12 @@ public class ImageConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
// 发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
// 添加抄送
|
||||
foreach (var ccUser in ccUserList)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
|
||||
}
|
||||
|
||||
// 格式化邮件内容
|
||||
@@ -502,7 +514,7 @@ public class ImageConsumer(
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -128,7 +128,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -236,7 +236,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
|
||||
};
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -308,7 +308,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -353,7 +353,8 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
|
||||
};
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,7 +419,7 @@ public class UrgentIRApplyedReReadingConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == taskInfo.TrialId);
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -462,7 +463,7 @@ public class UrgentIRApplyedReReadingConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using DocumentFormat.OpenXml.Vml;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Command;
|
||||
@@ -56,7 +55,6 @@ public class UserSiteSurveySubmitedEventConsumer(
|
||||
|
||||
if (emailConfig != null)
|
||||
{
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
|
||||
|
||||
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId && t.TrialUser.IsDeleted == false)
|
||||
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
|
||||
@@ -69,8 +67,8 @@ public class UserSiteSurveySubmitedEventConsumer(
|
||||
|
||||
var toUserName = string.Empty;
|
||||
|
||||
//有SPM 并且参与
|
||||
if (trialInfo.IsSPMJoinSiteSurvey && sPMOrCPMList.Count > 0)
|
||||
//有SPM
|
||||
if (sPMOrCPMList.Count > 0)
|
||||
{
|
||||
foreach (var user in sPMOrCPMList)
|
||||
{
|
||||
@@ -98,6 +96,7 @@ public class UserSiteSurveySubmitedEventConsumer(
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
|
||||
|
||||
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
|
||||
|
||||
@@ -123,7 +122,7 @@ public class UserSiteSurveySubmitedEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -206,7 +205,7 @@ public class SiteSurveySPMSubmitedEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +283,7 @@ public class SiteSurverRejectedEventConsumer(
|
||||
{
|
||||
//没有SPM PM驳回到CRC
|
||||
|
||||
messageToSend.To.Add(new MailboxAddress(siteSurveyInfo.UserName, siteSurveyInfo.Email));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, siteSurveyInfo.Email));
|
||||
}
|
||||
|
||||
//发件地址
|
||||
@@ -320,7 +319,7 @@ public class SiteSurverRejectedEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -89,14 +89,14 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
|
||||
|
||||
foreach (var userinfo in userinfoList)
|
||||
{
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
}
|
||||
|
||||
var userNames = userinfoList.Select(x => x.FullName).ToList();
|
||||
|
||||
foreach (var pm in pmandAPm)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
|
||||
}
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -122,7 +122,7 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -181,11 +181,11 @@ public class CRCRepliedQCChallengeEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
foreach (var pm in pmandAPm)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
|
||||
}
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
|
||||
@@ -221,7 +221,7 @@ public class CRCRepliedQCChallengeEventConsumer(
|
||||
};
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,10 +307,10 @@ public class QCRepliedQCChallengeEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
if (craInfo != null)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
|
||||
}
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -339,7 +339,7 @@ public class QCRepliedQCChallengeEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,7 +439,7 @@ public class CRCRepliedCheckChallengeEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,10 +517,10 @@ public class PMRepliedCheckChallengeEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
if (craInfo != null)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
|
||||
}
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -549,7 +549,7 @@ public class PMRepliedCheckChallengeEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -620,7 +620,7 @@ public class CheckStateChangedToAuditEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
@@ -648,7 +648,7 @@ public class CheckStateChangedToAuditEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -707,11 +707,11 @@ public class QCClaimTaskEventConsumer(
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
foreach (var pm in pmandAPm)
|
||||
{
|
||||
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
|
||||
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
|
||||
}
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
|
||||
@@ -757,7 +757,7 @@ public class QCClaimTaskEventConsumer(
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
using IRaCIS.Core.Application.Helper.OtherTool;
|
||||
using IRaCIS.Core.Domain.BaseModel;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IRaCIS.Core.Domain.BaseModel;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
|
||||
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger, IConfiguration _config) : IFilter<ConsumeContext<T>> where T : DomainEvent
|
||||
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger) : IFilter<ConsumeContext<T>> where T : DomainEvent
|
||||
{
|
||||
|
||||
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
|
||||
@@ -25,44 +23,6 @@ public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logge
|
||||
{
|
||||
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
|
||||
_logger.LogError(errorInfo);
|
||||
|
||||
try
|
||||
{
|
||||
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
|
||||
|
||||
if (isOpenWeComNotice)
|
||||
{
|
||||
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
|
||||
|
||||
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
|
||||
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
|
||||
|
||||
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
|
||||
|
||||
|
||||
|
||||
// 🔔 异步告警(不要阻塞请求)
|
||||
_ = WeComNotifier.SendAlertAsync(
|
||||
webhook: webhook,
|
||||
alert: new WeComAlert
|
||||
{
|
||||
Env = baseUrl,
|
||||
UserName = "MassTransit 自动触发邮件",
|
||||
Api = "",
|
||||
Message = $"异常信息:{exception.Message} ",
|
||||
Stack = exception.StackTrace,
|
||||
AtUsers = userList ?? []
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"MassTransit里发送企业微信出现错误:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
@@ -27,7 +26,6 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
public class UrgentIRUnReadTaskRecurringEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
IRepository<Dictionary> _dictionaryRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
|
||||
@@ -37,15 +35,9 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
|
||||
public async Task Consume(ConsumeContext<UrgentIRUnReadTaskRecurringEvent> context)
|
||||
{
|
||||
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
|
||||
var scenario = EmailBusinessScenario.ExpeditedReading;
|
||||
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
|
||||
|
||||
@@ -169,7 +161,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
|
||||
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
|
||||
//处理标记已通知的任务
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public static class OldRecurringEmailHelper
|
||||
public static string EmailNamePlaceholder => StaticData.EmailSend.EmailNamePlaceholder;
|
||||
|
||||
|
||||
//发送项目邮件的通用方法 已经处理
|
||||
|
||||
public static async Task SendTrialEmailAsync(
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
@@ -28,13 +28,12 @@ public static class OldRecurringEmailHelper
|
||||
|
||||
Guid trialId, EmailBusinessScenario businessScenario,
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc,
|
||||
Guid? trialSiteId = null)
|
||||
Guid? trialSiteId = null, Guid? trialReadingCriterionId = null)
|
||||
{
|
||||
//找到配置
|
||||
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
|
||||
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
|
||||
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();
|
||||
|
||||
var trialInfo=await _trialRepository.Where(t=>t.Id== trialId).FirstOrDefaultAsync();
|
||||
|
||||
if (trialEmailConfig == null || trialEmailConfig.IsAutoSend == false || trialEmailConfig.IsEnable == false)
|
||||
{
|
||||
@@ -163,7 +162,7 @@ public static class OldRecurringEmailHelper
|
||||
if (sendEmailConfig != null)
|
||||
{
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
|
||||
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using MassTransit.Scheduling;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,7 +14,7 @@ using System.Threading.Tasks;
|
||||
namespace IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
|
||||
|
||||
//项目手动选择 周期性邮件
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -26,20 +25,14 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<QCImageQuestionRecurringEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<QCImageQuestionRecurringEvent> context)
|
||||
{
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
var isEn_us = false;
|
||||
|
||||
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
|
||||
|
||||
@@ -71,9 +64,8 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
user.FullName, DateTime.Now, sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
return (topicStr, htmlBodyStr, isEn_us, userId);
|
||||
};
|
||||
@@ -99,20 +91,13 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<CRCImageQuestionRecurringEvent>
|
||||
{
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<CRCImageQuestionRecurringEvent> context)
|
||||
{
|
||||
var trialId = context.Message.TrialId;
|
||||
|
||||
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
|
||||
var isEn_us = false;
|
||||
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr, t.DeclarationTypeEnumList }).FirstNotNullAsync();
|
||||
|
||||
//找到 该项目的IQC 用户Id
|
||||
@@ -146,10 +131,9 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
user.FullName, DateTime.Now, sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
|
||||
return (topicStr, htmlBodyStr, false, userId);
|
||||
@@ -174,20 +158,13 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
|
||||
IRepository<TrialUserRole> _trialUserRoleRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
|
||||
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
|
||||
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<ImageQCRecurringEvent>
|
||||
{
|
||||
|
||||
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
|
||||
public async Task Consume(ConsumeContext<ImageQCRecurringEvent> context)
|
||||
{
|
||||
var trialId=context.Message.TrialId;
|
||||
|
||||
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
var isEn_us = false;
|
||||
|
||||
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
|
||||
|
||||
@@ -222,12 +199,9 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
|
||||
|
||||
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
|
||||
{
|
||||
|
||||
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
|
||||
|
||||
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
|
||||
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
|
||||
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
|
||||
user.FullName, DateTime.Now, sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
|
||||
|
||||
return (topicStr, htmlBodyStr, false, userId);
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -42,12 +42,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
Console.WriteLine("发送定时过期提醒");
|
||||
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
var systemDocQuery =
|
||||
from sysDoc in _systemDocumentRepository.AsQueryable(false)
|
||||
from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
|
||||
@@ -94,7 +89,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
@@ -119,7 +114,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
if (emailConfig != null)
|
||||
{
|
||||
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
|
||||
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
|
||||
}
|
||||
|
||||
@@ -155,11 +150,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
|
||||
public async Task Consume(ConsumeContext<SystemDocumentPublishEvent> context)
|
||||
{
|
||||
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
// 记录是否只发送给新增角色的日志
|
||||
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
|
||||
@@ -239,7 +230,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,327 +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.Linq.Dynamic.Core;
|
||||
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 = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
|
||||
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,
|
||||
TrialId= trialDoc.TrialId,
|
||||
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 trialInfo = _trialRepository.Where(x => x.Id == userinfo.TrialId).FirstOrDefault();
|
||||
var messageToSend = new MimeMessage();
|
||||
//发件地址
|
||||
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
|
||||
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
|
||||
|
||||
|
||||
|
||||
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
|
||||
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
|
||||
{
|
||||
var 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, trialInfo);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生效通知
|
||||
/// </summary>
|
||||
public class TrialDocumentPublishEventConsumer(
|
||||
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
|
||||
IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<TrialDocument> _trialDocumentRepository,
|
||||
IRepository<IdentityUser> _identityUserRepository,
|
||||
IRepository<Trial> _trialRepository,
|
||||
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 = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
|
||||
|
||||
//设置当前事件传递过来的语言
|
||||
var culture = context.Message.CultureInfoName;
|
||||
CultureInfo.CurrentCulture = new CultureInfo(culture);
|
||||
|
||||
// 记录是否只发送给新增角色的日志
|
||||
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,
|
||||
TrialId= trialDoc.TrialId,
|
||||
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 trial in _trialRepository.AsQueryable(false) on trialDoc.TrialId equals trial.Id
|
||||
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,
|
||||
TrialId = trialDoc.TrialId,
|
||||
EmailFromName =trial.EmailFromName,
|
||||
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(userinfo.FullName, 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);
|
||||
|
||||
var trial = datalist.Where(x => x.ConfirmUserId == userinfo.Id).FirstOrDefault();
|
||||
|
||||
var trialInfo = await _trialRepository.Where(x=>x.Id==trial.TrialId).FirstNotNullAsync();
|
||||
|
||||
|
||||
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -17,9 +17,7 @@ using MassTransit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Threading.Tasks;
|
||||
using Subject = IRaCIS.Core.Domain.Models.Subject;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.Allocation;
|
||||
@@ -47,10 +45,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
IRepository<DicomSeries> _dicomSeriesRepository,
|
||||
IRepository<SubjectCanceDoctor> _subjectCanceDoctorRepository,
|
||||
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
|
||||
IRepository<ReadingNoneDicomMark> _readingNoneDicomMarkRepository,
|
||||
IRepository<ReadingNoneDicomMarkBinding> _readingNoneDicomMarkBindingRepository,
|
||||
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
|
||||
//IRepository<ReadingCustomTag> _readingCustomTagRepository,
|
||||
IRepository<ReadingCustomTag> _readingCustomTagRepository,
|
||||
IRepository<TaskInfluence> _taskInfluenceRepository,
|
||||
IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository,
|
||||
ILogger<VisitTaskService> _logger,
|
||||
@@ -768,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) };
|
||||
@@ -969,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()
|
||||
@@ -1017,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)
|
||||
//前序 不存在 未生成任务的访视
|
||||
@@ -1031,8 +1027,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
//满足前序访视不存在 需要签署但是未签署 sql 相当复杂 同时想查询所有未读的统计数字 就无法统计 byzhouhang
|
||||
//但是加字段 IsFrontTaskNeedSignButNotSign 那么签名临床数据的时候,要对该subject 该标准的有效的任务 这个字段需要在签名的时候维护 采取这种方式 统计数字灵活
|
||||
//.Where(t => t.Subject.SubjectVisitTaskList.AsQueryable().Where(visitTaskLambda).Any(c => c.IsNeedClinicalDataSign == true && c.IsClinicalDataSign == false && c.VisitTaskNum < t.VisitTaskNum))
|
||||
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate))
|
||||
;
|
||||
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate));
|
||||
|
||||
|
||||
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
|
||||
|
||||
@@ -1175,9 +1171,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
|
||||
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
|
||||
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
|
||||
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate))
|
||||
.WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "OCT").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true)
|
||||
.WhereIf(critrion.CriterionType == CriterionType.IVUS, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "IVUS").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true);
|
||||
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate));
|
||||
|
||||
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
|
||||
|
||||
@@ -1410,12 +1404,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
|
||||
task.ReReadingApplyState = ReReadingApplyState.TrialGroupHaveApplyed;
|
||||
|
||||
if (!isSPMjoin)
|
||||
{
|
||||
task.ReReadingApplyState = ReReadingApplyState.Agree;
|
||||
task.PMBackReason = applyReReadingCommand.RequestReReadingReason;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1627,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);
|
||||
@@ -1640,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);
|
||||
|
||||
@@ -1723,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任务
|
||||
@@ -1747,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审批 回退访视,因此这里不生成访视任务 影响多个标准的任务
|
||||
@@ -2087,9 +2071,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
{
|
||||
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
|
||||
{
|
||||
newTask.IsCopyLesionAnswer = true;
|
||||
|
||||
//CopyForms(newTask, origenalTask);
|
||||
CopyForms(newTask, origenalTask);
|
||||
}
|
||||
|
||||
|
||||
@@ -2187,6 +2169,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
|
||||
{
|
||||
CopyForms(newTask, origenalTask);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2196,8 +2179,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
{
|
||||
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
|
||||
{
|
||||
newTask.IsCopyLesionAnswer = true;
|
||||
//CopyForms(newTask, origenalTask);
|
||||
CopyForms(newTask, origenalTask);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2222,18 +2204,18 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
|
||||
private void CopyForms(VisitTask newTask, VisitTask origenalTask)
|
||||
{
|
||||
newTask.IsCopyLesionAnswer = true;
|
||||
|
||||
//自定义
|
||||
//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;
|
||||
|
||||
|
||||
|
||||
@@ -2264,7 +2246,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
|
||||
//ReadingTableAnswerRowInfo ReadingTableQuestionAnswer 一起加
|
||||
var readingTableAnswerRowInfoList = _readingTableAnswerRowInfoRepository.Where(t => t.VisitTaskId == origenalTask.Id).Include(t => t.LesionAnswerList).ToList();
|
||||
Dictionary<Guid, Guid> lesionRelationship = new Dictionary<Guid, Guid>() { };
|
||||
|
||||
foreach (var item in readingTableAnswerRowInfoList)
|
||||
{
|
||||
|
||||
@@ -2272,7 +2254,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
var originalFristAddTaskId = item.FristAddTaskId;
|
||||
|
||||
var newRowId = NewId.NextSequentialGuid();
|
||||
lesionRelationship.Add(item.Id, newRowId);
|
||||
|
||||
foreach (var mark in readingTaskQuestionMarkList)
|
||||
{
|
||||
mark.RowId = mark.RowId == item.Id ? newRowId : mark.RowId;
|
||||
@@ -2294,54 +2276,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in readingTableAnswerRowInfoList)
|
||||
{
|
||||
if (item.SplitRowId != null && lesionRelationship.ContainsKey(item.SplitRowId.Value))
|
||||
{
|
||||
item.SplitRowId = lesionRelationship[item.SplitRowId.Value];
|
||||
}
|
||||
|
||||
if (item.MergeRowId != null && lesionRelationship.ContainsKey(item.MergeRowId.Value))
|
||||
{
|
||||
item.MergeRowId = lesionRelationship[item.MergeRowId.Value];
|
||||
}
|
||||
}
|
||||
|
||||
_ = _readingTaskQuestionMarkRepository.AddRangeAsync(readingTaskQuestionMarkList).Result;
|
||||
_ = _readingTableAnswerRowInfoRepository.AddRangeAsync(readingTableAnswerRowInfoList).Result;
|
||||
|
||||
|
||||
var noneMarkList = _readingNoneDicomMarkRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
|
||||
|
||||
var noneMarkBindingList = _readingNoneDicomMarkBindingRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
|
||||
|
||||
|
||||
foreach (var item in noneMarkList)
|
||||
{
|
||||
var newid = NewId.NextSequentialGuid();
|
||||
|
||||
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
|
||||
item.VisitTaskId = newTask.Id;
|
||||
foreach (var item1 in noneMarkBindingList)
|
||||
{
|
||||
if (item1.NoneDicomMarkId == item.Id)
|
||||
{
|
||||
item1.NoneDicomMarkId = newid;
|
||||
}
|
||||
}
|
||||
|
||||
item.Id = newid;
|
||||
}
|
||||
|
||||
|
||||
foreach (var item in noneMarkBindingList)
|
||||
{
|
||||
item.Id = NewId.NextSequentialGuid();
|
||||
item.VisitTaskId = newTask.Id;
|
||||
}
|
||||
|
||||
_ = _readingNoneDicomMarkRepository.AddRangeAsync(noneMarkList).Result;
|
||||
_ = _readingNoneDicomMarkBindingRepository.AddRangeAsync(noneMarkBindingList).Result;
|
||||
}
|
||||
|
||||
|
||||
@@ -2434,10 +2375,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
//另一个阅片人的任务根据任务进度自动进入PM退回或PM申请重阅
|
||||
filterExpression = filterExpression.And(t => t.VisitTaskNum >= task.VisitTaskNum);
|
||||
|
||||
//退回只影响有序的后续所有的,无序的当前访视
|
||||
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
|
||||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == task.SourceSubjectVisitId));
|
||||
|
||||
|
||||
var influenceTaskList = await _visitTaskRepository.Where(filterExpression, true).ToListAsync();
|
||||
|
||||
@@ -2595,12 +2532,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
|
||||
//删除序列数据
|
||||
await _subjectCriteriaEvaluationVisitStudyFilterRepository.BatchDeleteNoTrackingAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && t.SubjectVisitId == task.SourceSubjectVisitId);
|
||||
|
||||
otherVisitIdList = otherVisitIdList.Where(t => t != task.SourceSubjectVisitId.Value).ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//BM后续访视 ,筛选状态不变,任务生成状态重置(实际该访视任务状态 可能是重阅重置了或者失效了,需要后续生成,或者取消分配了,需要后续重新分配)
|
||||
await _subjectCriteriaEvaluationVisitFilterRepository.UpdatePartialFromQueryAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && otherVisitIdList.Contains(t.SubjectVisitId),
|
||||
t => new SubjectCriteriaEvaluationVisitFilter()
|
||||
@@ -2652,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)
|
||||
{
|
||||
@@ -2702,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;
|
||||
|
||||
@@ -2720,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任务
|
||||
@@ -2748,27 +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);
|
||||
|
||||
//退回只影响有序的后续所有的,无序的当前访视
|
||||
if (isReReading == false)
|
||||
{
|
||||
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
|
||||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == filterObj.SourceSubjectVisitId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3009,8 +2931,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
|
||||
}
|
||||
#endregion
|
||||
|
||||
//IsIRAppyTaskInfluenced 列表中存在IR已申请重阅的任务!
|
||||
return ResponseOutput.Ok(list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
|
||||
|
||||
return (list, new { IsIRAppyTaskInfluenced = isIRAppyTaskInfluenced });
|
||||
|
||||
}
|
||||
|
||||
@@ -3092,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,187 +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 GetReSendEmailInDto : PageInput
|
||||
{
|
||||
public Guid Id { 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;
|
||||
}
|
||||
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 Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class SynchronizationEmailInDto
|
||||
{
|
||||
public Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class ResendEmailInDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid? TrialId { get; set; }
|
||||
}
|
||||
|
||||
public class EmailAuthorization
|
||||
{
|
||||
public string FromEmail { get; set; } = string.Empty;
|
||||
|
||||
public string AuthorizationCode { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
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,768 +0,0 @@
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// 此代码由liquid模板自动生成 byzhouhang 20240909
|
||||
// 生成时间 2025-10-28 06:22:42Z
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
||||
//--------------------------------------------------------------------
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using IdentityModel.Client;
|
||||
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 MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using MailKit.Security;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using Panda.DynamicWebApi.Attributes;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
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<EmailReSendLog> _emailReSendLog,
|
||||
|
||||
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<PageOutput<EmailLogView>> GetReSendEmail(GetReSendEmailInDto inDto)
|
||||
{
|
||||
var messageId = await _emailLogRepository.Where(x => x.Id == inDto.Id).Select(x => x.MessageId).FirstOrDefaultAsync();
|
||||
|
||||
var reSendMessagelist = await _emailReSendLog.Where(x => x.MainMailMessageId == messageId).Select(x => x.ReMailMessageId).ToListAsync();
|
||||
var emailLogQueryable = _emailLogRepository
|
||||
|
||||
.Where(x => reSendMessagelist.Contains(x.MessageId))
|
||||
.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
|
||||
{
|
||||
await AuthenticateImap(client,inDto.TrialId);
|
||||
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
|
||||
{
|
||||
await AuthenticateImap(client,inDto.TrialId);
|
||||
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();
|
||||
|
||||
SystemEmailSendConfig sendConfig = new SystemEmailSendConfig()
|
||||
{
|
||||
};
|
||||
|
||||
if (inDto.TrialId != null)
|
||||
{
|
||||
sendConfig = await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => new SystemEmailSendConfig()
|
||||
{
|
||||
Host = _systemEmailConfig.Host,
|
||||
Port = _systemEmailConfig.Port,
|
||||
FromEmail = x.EmailFromEmail,
|
||||
AuthorizationCode = x.EmailAuthorizationCode
|
||||
}).FirstNotNullAsync();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sendConfig = _systemEmailConfig;
|
||||
}
|
||||
|
||||
var msgid= await SendEmailHelper.SendEmailAsync(messageToSend, sendConfig);
|
||||
|
||||
|
||||
await _emailReSendLog.AddAsync(new EmailReSendLog()
|
||||
{
|
||||
MainMailMessageId= emailInfo.MessageId,
|
||||
ReMailMessageId= msgid
|
||||
});
|
||||
|
||||
await _emailReSendLog.SaveChangesAsync();
|
||||
|
||||
await SynchronizationEmail(new SynchronizationEmailInDto() {
|
||||
|
||||
TrialId= inDto.TrialId
|
||||
});
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步邮件
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IResponseOutput> SynchronizationEmail(SynchronizationEmailInDto inDto)
|
||||
{
|
||||
|
||||
var fromEmail = _systemEmailConfig.FromEmail;
|
||||
|
||||
|
||||
if (inDto.TrialId != null)
|
||||
{
|
||||
fromEmail= await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => x.EmailFromEmail).FirstNotNullAsync();
|
||||
|
||||
}
|
||||
|
||||
var maxTime = await _emailLogRepository.Where(x=>x.SenderAddress== fromEmail).MaxAsync(t => t.EmailDate);
|
||||
var startDate = maxTime ?? DateTime.MinValue;
|
||||
List<EmailLog> emailList = new List<EmailLog>();
|
||||
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
|
||||
|
||||
// 第一步:同步发件箱邮件
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
await AuthenticateImap(client, inDto.TrialId);
|
||||
var sentFolder = OpenSentFolder(client);
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
Console.WriteLine($"同步发件箱邮件时出错: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存同步的发件箱邮件
|
||||
await _emailLogRepository.AddRangeAsync(emailList);
|
||||
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
|
||||
await _emailLogRepository.SaveChangesAsync();
|
||||
|
||||
// 第二步:处理收件箱中的邮件发送失败通知
|
||||
try
|
||||
{
|
||||
await ProcessInboxFailureNotifications(startDate,inDto.TrialId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理收件箱失败通知邮件时出错: {ex.Message}");
|
||||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||||
// 即使处理失败通知邮件出错,也不影响整体同步操作的成功
|
||||
// 记录警告但不中断主流程
|
||||
}
|
||||
|
||||
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 IMailFolder OpenInboxFolder(ImapClient client)
|
||||
{
|
||||
IMailFolder folder = null;
|
||||
try
|
||||
{
|
||||
folder = client.GetFolder(SpecialFolder.All);
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
var candidates = new[] { "收件箱", "Inbox" };
|
||||
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, "Inbox", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("未找到收件箱文件夹");
|
||||
|
||||
folder.Open(FolderAccess.ReadOnly);
|
||||
return folder;
|
||||
}
|
||||
|
||||
private async Task AuthenticateImap(ImapClient client, Guid? trialId)
|
||||
{
|
||||
try
|
||||
{
|
||||
EmailAuthorization authorization = new EmailAuthorization()
|
||||
{
|
||||
FromEmail = _systemEmailConfig.FromEmail,
|
||||
AuthorizationCode = _systemEmailConfig.AuthorizationCode,
|
||||
};
|
||||
|
||||
if (trialId != null)
|
||||
{
|
||||
authorization = await _trialRepository.Where(x => x.Id == trialId.Value).Select(x => new EmailAuthorization()
|
||||
{
|
||||
AuthorizationCode = x.EmailAuthorizationCode,
|
||||
FromEmail = x.EmailFromEmail
|
||||
}).FirstNotNullAsync();
|
||||
}
|
||||
|
||||
client.Connect(_systemEmailConfig.Imap, _systemEmailConfig.ImapPort, SecureSocketOptions.SslOnConnect);
|
||||
client.Authenticate(authorization.FromEmail, authorization.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理收件箱中的邮件发送失败通知
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task ProcessInboxFailureNotifications(DateTime startTime, Guid? trialId)
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
await AuthenticateImap(client, trialId);
|
||||
var inboxFolder = OpenInboxFolder(client);
|
||||
|
||||
// 搜索可能包含失败通知的邮件
|
||||
// 通常失败通知邮件会包含"失败"、"错误"、"undeliverable"、"delivery failed"等关键词
|
||||
var searchQuery = SearchQuery.All.Or(SearchQuery.SubjectContains("失败"))
|
||||
.Or(SearchQuery.SubjectContains("错误"))
|
||||
.Or(SearchQuery.SubjectContains("undeliverable"))
|
||||
.Or(SearchQuery.SubjectContains("delivery failed"))
|
||||
.Or(SearchQuery.SubjectContains("Delivery Status Notification"))
|
||||
.Or(SearchQuery.BodyContains("失败"))
|
||||
.Or(SearchQuery.BodyContains("错误"))
|
||||
.Or(SearchQuery.BodyContains("undeliverable"))
|
||||
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
|
||||
|
||||
var uids = inboxFolder.Search(searchQuery).ToList();
|
||||
var processedCount = 0;
|
||||
var updatedCount = 0;
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = inboxFolder.GetMessage(uid);
|
||||
|
||||
// 尝试从邮件内容中提取原始邮件的Message-Id或相关信息
|
||||
var originalMessageId = ExtractOriginalMessageId(message);
|
||||
if (!string.IsNullOrEmpty(originalMessageId))
|
||||
{
|
||||
// 查找对应的EmailLog记录
|
||||
var emailLog = await _emailLogRepository
|
||||
.Where(x => x.MessageId == originalMessageId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
|
||||
{
|
||||
EmailStateEnum = EmailState.Error,
|
||||
ErrorInfo = message.TextBody
|
||||
|
||||
});
|
||||
|
||||
updatedCount++;
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inboxFolder.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
|
||||
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"断开IMAP客户端连接时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从失败通知邮件中提取原始邮件的Message-Id
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
private string ExtractOriginalMessageId(MimeMessage message)
|
||||
{
|
||||
// 首先检查邮件头中是否有原始Message-Id
|
||||
var originalMessageId = message.Headers["Original-Message-ID"] ??
|
||||
message.Headers["X-Original-Message-ID"] ??
|
||||
message.Headers["In-Reply-To"];
|
||||
|
||||
if (!string.IsNullOrEmpty(originalMessageId))
|
||||
{
|
||||
// 清理Message-Id格式(移除尖括号)
|
||||
originalMessageId = originalMessageId.Trim('<', '>');
|
||||
return originalMessageId;
|
||||
}
|
||||
|
||||
// 检查邮件附件中是否包含.eml文件(这是最常见的失败通知格式)
|
||||
foreach (var attachment in message.Attachments)
|
||||
{
|
||||
if (attachment is MessagePart messagePart)
|
||||
{
|
||||
// 直接获取嵌入的邮件消息
|
||||
var originalMessageIdFromAttachment = messagePart.Message?.MessageId;
|
||||
if (!string.IsNullOrEmpty(originalMessageIdFromAttachment))
|
||||
{
|
||||
return originalMessageIdFromAttachment.Trim('<', '>');
|
||||
}
|
||||
}
|
||||
else if (attachment is MimePart mimePart)
|
||||
{
|
||||
// 检查文件扩展名是否为.eml
|
||||
var fileName = mimePart.FileName ?? mimePart.ContentType.Name;
|
||||
if (!string.IsNullOrEmpty(fileName) &&
|
||||
(fileName.EndsWith(".eml", StringComparison.OrdinalIgnoreCase) ||
|
||||
mimePart.ContentType.MimeType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析.eml附件内容
|
||||
using var memoryStream = new MemoryStream();
|
||||
mimePart.Content.DecodeTo(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
var originalMessage = MimeMessage.Load(memoryStream);
|
||||
if (!string.IsNullOrEmpty(originalMessage?.MessageId))
|
||||
{
|
||||
return originalMessage.MessageId.Trim('<', '>');
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"解析.eml附件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果邮件头和附件中都没有,尝试从邮件正文中提取
|
||||
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
|
||||
|
||||
// 尝试匹配常见的Message-Id格式
|
||||
var messageIdPattern = @"(?:Message-ID|Message-Id|message-id):\s*<?([^>\s]+)>?";
|
||||
var match = System.Text.RegularExpressions.Regex.Match(content, messageIdPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
// 尝试从邮件引用部分提取
|
||||
var referencePattern = @"^>.*Message-ID:\s*<?([^>\s]+)>?";
|
||||
var matches = System.Text.RegularExpressions.Regex.Matches(content, referencePattern, System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
return matches[0].Groups[1].Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+68
-756
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user