Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing Details

IRC_NewDev
he 2024-07-05 09:09:08 +08:00
commit 1e5f01fdeb
63 changed files with 5225 additions and 22 deletions

View File

@ -0,0 +1,37 @@
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)));
}
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace IRaCIS.Core.SCP.Filter
{
#region snippet_DisableFormValueModelBindingAttribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
//factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
context.HttpContext.Request.EnableBuffering();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
#endregion
}

View File

@ -0,0 +1,61 @@
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;//标记当前异常已经被处理过了
}
}
}

View File

@ -0,0 +1,120 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Application.Services.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(StaticData.International("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;
}
}
}

View File

@ -0,0 +1,62 @@

using FellowOakDicom.Imaging;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using System.Security.Cryptography;
using System.Text;
namespace IRaCIS.Core.Application.Helper;
public static class ImageHelper
{
// 采用ImageSharp组件 跨平台不依赖windows 平台图像处理组件,这里大坑(net 6 下之前由于Dicom处理最新组件 依赖了这个库的一个旧版本导致缩略图bug单独升级依赖组件才解决)
public static void ResizeSave(string filePath, string? fileStorePath)
{
fileStorePath = fileStorePath ?? filePath + ".preview.jpeg";
using (var image = SixLabors.ImageSharp.Image.Load(filePath))
{
image.Mutate(x => x.Resize(500, 500));
image.Save(fileStorePath);
}
}
public static Stream RenderPreviewJpeg(string filePath)
{
string jpegPath = filePath + ".preview.jpg";
if (!File.Exists(jpegPath))
{
using (Stream stream = new FileStream(jpegPath, FileMode.Create))
{
DicomImage image = new DicomImage(filePath);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(stream, new JpegEncoder());
}
}
return new FileStream(jpegPath, FileMode.Open);
}
public static void RemovePreviewJpeg(string filePath)
{
string jpegPath = filePath + ".preview.jpg";
if (File.Exists(jpegPath)) File.Delete(jpegPath);
}
}

View File

@ -0,0 +1,57 @@
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.Application.Services;
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 + "IRaCIS.Core.SCP.dll");
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
.PropertiesAutowired().AsImplementedInterfaces();
containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
}
}
}

View File

@ -0,0 +1,44 @@
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using Medallion.Threading.SqlServer;
using Microsoft.EntityFrameworkCore;
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.AddScoped<DbContext, IRaCISDBContext>();
//这个注入没有成功--注入是没问题的构造函数也只是支持参数就好错在注入的地方不能写DbContext
//Web程序中通过重用池中DbContext实例可提高高并发场景下的吞吐量 这在概念上类似于ADO.NET Provider原生的连接池操作方式具有节省DbContext实例化成本的优点
services.AddDbContext<IRaCISDBContext>(options =>
{
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value,
contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.EnableSensitiveDataLogging();
options.AddInterceptors(new QueryWithNoLockDbCommandInterceptor());
options.UseProjectables();
});
//注意区分 easy caching 也有 IDistributedLockProvider
services.AddSingleton<IDistributedLockProvider>(sp =>
{
//var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
return new SqlDistributedSynchronizationProvider(configuration.GetSection("ConnectionStrings:RemoteNew").Value);
});
//services.AddAssemblyTriggers(typeof(SubjectVisitImageDateTrigger).Assembly);
}
}
}

View File

@ -0,0 +1,59 @@

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;
});
}
}
}

View File

@ -0,0 +1,36 @@
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;
}
}
}

View File

@ -0,0 +1,42 @@
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);
}
}
}
}

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="DistributedLock.Core" Version="1.0.6" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.4" />
<PackageReference Include="fo-dicom" Version="5.1.2" />
<PackageReference Include="fo-dicom.Codecs" Version="5.12.0" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.4" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.2" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="NewId" Version="4.0.1" />
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.0.3" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IRaCIS.Core.Domain\IRaCIS.Core.Domain.csproj" />
<ProjectReference Include="..\IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj" />
</ItemGroup>
</Project>

208
IRC.Core.SCP/Program.cs Normal file
View File

@ -0,0 +1,208 @@
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 兼容windows 服务命令行的方式
int urlsIndex = Array.FindIndex(args, arg => arg != null && arg.StartsWith("--port"));
if (urlsIndex > -1)
{
var port = args[urlsIndex].Substring("--port=".Length);
Console.WriteLine(port);
builder.WebHost.UseUrls($"http://0.0.0.0:{port}");
}
#endregion
#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<LogActionFilter>();
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);
//转发头设置 获取真实IP
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
//Dicom影像渲染图片 跨平台
//builder.Services.AddDicomSetup();
new DicomSetupBuilder()
.RegisterServices(s =>
s.AddFellowOakDicom()
.AddTranscoderManager<NativeTranscoderManager>()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>())
.SkipValidation()
.Build();
#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.Information)
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient.HttpReports", LogEventLevel.Warning)
.Enrich.WithClientIp()
.Enrich.FromLogContext()
//控制台 方便调试 问题 我们显示记录日志 时 获取上下文的ip 和用户名 用户类型
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Warning,
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine}")
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine}")
.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
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services);
app.Run();

View File

@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36358",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5243",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,118 @@
using AutoMapper;
using IRaCIS.Application.Services.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 IRepository _repository { 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(_repository))]
public IRepository _repository { 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(_repository))]
public IRepository _repository { 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 IRepository _repository { 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
}

View File

@ -0,0 +1,358 @@
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,
DicomTransferSyntax.JPEG2000Lossless,
DicomTransferSyntax.JPEGProcess14SV1,
DicomTransferSyntax.JPEGProcess14,
DicomTransferSyntax.RLELossless,
// Lossy
DicomTransferSyntax.JPEGLSNearLossless,
DicomTransferSyntax.JPEG2000Lossy,
DicomTransferSyntax.JPEGProcess1,
DicomTransferSyntax.JPEGProcess2_4,
// Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies)
: base(stream, fallbackEncoding, log, dependencies)
{
}
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();
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 ).FirstOrDefault();
if (findTrialSiteAE!=null)
{
_trialSiteId= findTrialSiteAE.TrialSiteId;
isCanReceiveIamge = true;
}
}
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge==false)
{
Log.Logger.Warning($"拒绝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;
await _SCPImageUploadRepository.AddAsync(_upload, true);
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 seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid);
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid);
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);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FindAsync(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));
}
}
}

View File

@ -0,0 +1,337 @@
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;
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)
{
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetString(DicomTag.PatientID);
//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);
var findStudy = await _studyRepository.FindAsync(studyId);
var findSerice = await _seriesRepository.FindAsync(seriesId);
var findInstance = await _instanceRepository.FindAsync(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;
}
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
};
++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 });
}
//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);
}
}
}

View File

@ -0,0 +1,11 @@
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);
}
}

View File

@ -0,0 +1,427 @@
using Aliyun.OSS;
using IRaCIS.Core.Infrastructure;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Minio.DataModel.Args;
using Minio;
using SharpCompress.Common;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.SCP
{
public class MinIOOptions : AWSOptions
{
public int port { get; set; }
}
public class AWSOptions
{
public string endPoint { get; set; }
public bool useSSL { get; set; }
public string accessKey { get; set; }
public string secretKey { get; set; }
public string bucketName { get; set; }
public string viewEndpoint { get; set; }
}
public class AliyunOSSOptions
{
public string regionId { get; set; }
public string accessKeyId { get; set; }
public string accessKeySecret { 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 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 AliyunOSSOptions AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSOptions AWS { get; set; }
}
public class AliyunOSSTempToken
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string SecurityToken { get; set; }
public string Expiration { get; set; }
public string Region { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
}
public enum ObjectStoreUse
{
AliyunOSS = 0,
MinIO = 1,
AWS = 2,
}
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 class OSSService : IOSSService
{
public ObjectStoreServiceOptions ObjectStoreServiceOptions { 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>
/// <returns></returns>
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
{
var ossRelativePath = isFileNameAddGuid? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}": $"{oosFolderPath}/{fileRealName}";
//var ossRelativePath = 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(aliConfig.endPoint, aliConfig.accessKeyId, aliConfig.accessKeySecret);
// 上传文件
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.accessKey, minIOConfig.secretKey).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 minIOConfig = ObjectStoreServiceOptions.AWS;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.endPoint}")
.WithCredentials(minIOConfig.accessKey, minIOConfig.secretKey).WithSSL(minIOConfig.useSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.bucketName)
.WithObject(ossRelativePath)
.WithStreamData(memoryStream)
.WithObjectSize(memoryStream.Length);
await minioClient.PutObjectAsync(putObjectArgs);
}
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>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
{
var localFileName = Path.GetFileName(localFilePath);
var ossRelativePath = isFileNameAddGuid? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
//var ossRelativePath = oosFolderPath + "/" + localFileName;
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(aliConfig.endPoint, aliConfig.accessKeyId, aliConfig.accessKeySecret);
// 上传文件
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.accessKey, minIOConfig.secretKey).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 minIOConfig = ObjectStoreServiceOptions.AWS;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.endPoint}")
.WithCredentials(minIOConfig.accessKey, minIOConfig.secretKey).WithSSL(minIOConfig.useSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.bucketName)
.WithObject(ossRelativePath)
.WithFileName(localFilePath);
await minioClient.PutObjectAsync(putObjectArgs);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
return "/" + ossRelativePath;
}
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(aliConfig.endPoint, aliConfig.accessKeyId, aliConfig.accessKeySecret);
// 上传文件
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.accessKey, minIOConfig.secretKey).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 minIOConfig = ObjectStoreServiceOptions.AWS;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.endPoint}")
.WithCredentials(minIOConfig.accessKey, minIOConfig.secretKey).WithSSL(minIOConfig.useSSL)
.Build();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.bucketName)
.WithObject(ossRelativePath)
.WithFile(localFilePath);
await minioClient.GetObjectAsync(getObjectArgs);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss下载失败!" + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(aliConfig.endPoint, aliConfig.accessKeyId, aliConfig.accessKeySecret);
// 生成签名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.accessKey, minIOConfig.secretKey).WithSSL(minIOConfig.useSSL)
.Build();
//var reqParams = new Dictionary<string, string>(StringComparer.Ordinal)
// {
// { "response-content-type", "application/json" }
// };
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 minIOConfig = ObjectStoreServiceOptions.AWS;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.endPoint}")
.WithCredentials(minIOConfig.accessKey, minIOConfig.secretKey).WithSSL(minIOConfig.useSSL)
.Build();
return string.Empty;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss授权url失败!" + ex.Message);
}
}
}
}

View File

@ -0,0 +1,63 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endpoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-irc-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endpoint": "http://192.168.3.68",
"port": "8001",
"useSSL": false,
"accessKey": "IDFkwEpWej0b4DtiuThL",
"secretKey": "Lhuu83yMhVwu7c1SnjvGY6lq74jzpYqifK6Qtj4h",
"bucketName": "test"
}
},
"ConnectionStrings": {
//"RemoteNew": "Server=47.117.165.18,1434;Database=Prod_Study;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=47.117.165.18,1434;Database=Prod_Study_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_Study;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_Study_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "study@extimaging.com",
"FromName": "研究单位阅片系统",
"AuthorizationCode": "zhanying123",
"SiteUrl": "https://study.extimaging.com/login"
}
}

View File

@ -0,0 +1,79 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endPoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-irc-test-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endPoint": "106.14.89.110",
"port": "9001",
"useSSL": false,
"accessKey": "fbStsVYCIPKHQneeqMwD",
"secretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
"bucketName": "hir-test",
"viewEndpoint": "http://106.14.89.110:9001/hir-test/"
},
"AWS": {
"endPoint": "s3.us-east-1.amazonaws.com",
"useSSL": false,
"accessKey": "AKIAZQ3DRSOHFPJJ6FEU",
"secretKey": "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",
"bucketName": "ei-irc-test-store",
"viewEndpoint": "https://ei-irc-test-store.s3.amazonaws.com/"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_HIR;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=106.14.89.110,1435;Database=Test_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "test-study@extimaging.com",
"FromName": "Test_Study",
"AuthorizationCode": "zhanying123",
"SiteUrl": "http://study.test.extimaging.com/login"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP",
"Value1",
"Value2",
"Value3"
],
"ServerPort": 11112
}
}

View File

@ -0,0 +1,76 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "MinIO",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endpoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-sir-test-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-sir-test-store.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endPoint": "44.218.11.19",
"port": "9001",
"useSSL": false,
"accessKey": "lH8DkKskLuDqPaiubuSQ",
"secretKey": "pdPdicvvLeH7xAC5yFUrI7odMyBfOXxvVWMvKYV4",
"bucketName": "hir-us",
"viewEndpoint": "http://hir.us.extimaging.com/oss/hir-us"
},
"AWS": {
"endPoint": "s3.us-east-1.amazonaws.com",
"useSSL": false,
"accessKey": "AKIAZQ3DRSOHFPJJ6FEU",
"secretKey": "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",
"bucketName": "ei-irc-test-store",
"viewEndpoint": "https://ei-irc-test-store.s3.amazonaws.com/"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=44.218.11.19,1435;Database=US_HIR;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=44.218.11.19,1435;Database=US_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "uat-study@extimaging.com",
"FromName": "Uat_Study",
"AuthorizationCode": "zhanying123",
"SiteUrl": "http://study.uat.extimaging.com/login"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP",
"Value1",
"Value2",
"Value3"
],
"ServerPort": 11112
}
}

View File

@ -0,0 +1,76 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "MinIO",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endpoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-irc-uat-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endPoint": "47.117.164.182",
"port": "9001",
"useSSL": false,
"accessKey": "b9Ul0e98xPzt6PwRXA1Q",
"secretKey": "DzMaU2L4OXl90uytwOmDXF2encN0Jf4Nxu2XkYqQ",
"bucketName": "hir-uat",
"viewEndpoint": "http://hir.uat.extimaging.com/oss/hir-uat"
},
"AWS": {
"endPoint": "s3.us-east-1.amazonaws.com",
"useSSL": false,
"accessKey": "AKIAZQ3DRSOHFPJJ6FEU",
"secretKey": "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",
"bucketName": "ei-irc-test-store",
"viewEndpoint": "https://ei-irc-test-store.s3.amazonaws.com/"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=47.117.164.182,1434;Database=Uat_HIR;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=47.117.164.182,1434;Database=Uat_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "uat-study@extimaging.com",
"FromName": "Uat_Study",
"AuthorizationCode": "zhanying123",
"SiteUrl": "http://study.uat.extimaging.com/login"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP",
"Value1",
"Value2",
"Value3"
],
"ServerPort": 11112
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infra.EFCore",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure", "IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj", "{07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -51,6 +53,10 @@ Global
{07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07EED0F8-08E6-46F3-ACBE-17BC1391BD4C}.Release|Any CPU.Build.0 = Release|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -402,6 +402,19 @@ namespace IRaCIS.Core.API.Controllers
return result;
}
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")]
[UnitOfWork]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt)
{
opt.Data.IsTrialPACSConfirmed = true;
var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _trialConfigService.ConfigTrialPACSInfo(opt.Data);
await _inspectionService.CompletedSign(singid, result);
return result;
}
/// <summary>
/// 签名确认
/// </summary>

View File

@ -64,22 +64,21 @@
<PackageReference Include="aliyun-net-sdk-sts" Version="3.1.2" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="EasyCaching.Interceptor.Castle" Version="1.9.2" />
<PackageReference Include="EasyCaching.Serialization.MessagePack" Version="1.9.2">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.12" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.12" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageReference Include="Invio.Extensions.Authentication.JwtBearer" Version="2.0.1" />
<PackageReference Include="LogDashboard" Version="1.4.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.0.3" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.6.2" />
</ItemGroup>

View File

@ -707,6 +707,11 @@
<param name="_trialRepository"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.ExploreRecommendService">
<summary>
ExploreRecommendService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Service.InternationalizationService">
<summary>
InternationalizationService
@ -9808,6 +9813,24 @@
<param name="_dicomInstanceRepository"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialDicomAEService">
<summary>
DicomAEService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialDicomAEService.GetTrialDicomAE(System.Guid)">
<summary>
获取项目dicom AE 配置信息otherinfo里面有IsPACSConnect IsTrialPACSConfirmed
</summary>
<param name="trialId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.TrialDicomAEService.TestSCPServerConnect(System.Guid)">
<summary>
测试scp server 是否可以连接
</summary>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialExternalUserService">
<summary>
项目外部人员 录入流程相关
@ -9820,6 +9843,11 @@
<param name="addOrEditTrialExternalUser"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.Application.Service.TrialSiteDicomAEService">
<summary>
TrialSiteDicomAEService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TaskAllocationRuleView">
<summary> TaskAllocationRuleView 列表视图模型 </summary>
</member>
@ -9960,6 +9988,15 @@
<member name="T:IRaCIS.Core.Application.ViewModel.CommonDocumentAddOrEdit">
<summary> CommonDocumentAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.ExploreRecommendView">
<summary> ExploreRecommendView 列表视图模型 </summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.ExploreRecommendQuery">
<summary>ExploreRecommendQuery 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.ExploreRecommendAddOrEdit">
<summary> ExploreRecommendAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.FrontAuditConfigView">
<summary> FrontAuditConfigView 列表视图模型 </summary>
</member>
@ -10959,6 +10996,15 @@
<member name="T:IRaCIS.Core.Application.ViewModel.UserWLTemplateAddOrEdit">
<summary> UserWLTemplateAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.DicomAEView">
<summary> DicomAEView 列表视图模型 </summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.DicomAEQuery">
<summary>DicomAEQuery 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.DicomAEAddOrEdit">
<summary> DicomAEAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TrialExternalUserView">
<summary> TrialExternalUserView 列表视图模型 </summary>
</member>
@ -10968,6 +11014,15 @@
<member name="T:IRaCIS.Core.Application.ViewModel.TrialExternalUserAddOrEdit">
<summary> TrialExternalUserAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TrialSiteDicomAEView">
<summary> TrialSiteDicomAEView 列表视图模型 </summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TrialSiteDicomAEQuery">
<summary>TrialSiteDicomAEQuery 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TrialSiteDicomAEAddOrEdit">
<summary> TrialSiteDicomAEAddOrEdit 列表查询参数模型</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TrialUserPreparationView">
<summary> TrialUserPreparation View 列表视图模型 </summary>
</member>
@ -11002,6 +11057,11 @@
ICommonDocumentService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Interfaces.IExploreRecommendService">
<summary>
IExploreRecommendService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Interfaces.IFrontAuditConfigService">
<summary>
IFrontAuditConfigService
@ -11052,11 +11112,21 @@
IOrganInfoService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Interfaces.IDicomAEService">
<summary>
IDicomAEService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Interfaces.ITrialExternalUserService">
<summary>
ITrialExternalUserService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Interfaces.ITrialSiteDicomAEService">
<summary>
ITrialSiteDicomAEService
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Contracts.EmailNoticeConfigView">
<summary> EmailNoticeConfigView 列表视图模型 </summary>
</member>
@ -13040,6 +13110,13 @@
<param name="trialConfig"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.TrialConfigService.ConfigTrialPACSInfo(IRaCIS.Core.Application.Contracts.TrialPACSConfig)">
<summary>
配置pacs信息
</summary>
<param name="trialConfig"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.TrialConfigService.UpdateTrialState(System.Guid,System.String,System.String)">
<summary>
更新项目状态
@ -14971,6 +15048,41 @@
<param name="status">9-拒绝入组10-确认入组</param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.PatientService.GetSCPImageUploadList(IRaCIS.Application.Contracts.SCPImageUploadQuery)">
<summary>
scp 影像推送记录表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.PatientService.GetPatientList(IRaCIS.Application.Contracts.PatientTrialQuery)">
<summary>
影像检查列表-患者为维度组织
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.PatientService.GetPatientStudyList(IRaCIS.Application.Contracts.PatientStudyInfoQuery)">
<summary>
影像检查列表-> 获取患者的检查列表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.PatientService.GetVisitPatientStudyFilterList(IRaCIS.Application.Contracts.VisitPatientStudyFilterQuery)">
<summary>
影像访视上传 检查列表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.PatientService.SubmitVisitStudyBinding(IRaCIS.Application.Contracts.SubmitVisitStudyBindingCommand)">
<summary>
提交 患者检查和访视的绑定
</summary>
<param name="inCommand"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Application.Services.SubjectService.AddOrUpdateSubject(IRaCIS.Application.Contracts.SubjectCommand)">
<summary>
添加或更新受试者信息[New]

View File

@ -0,0 +1,61 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 09:29:36
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.ViewModel
{
/// <summary> ExploreRecommendView 列表视图模型 </summary>
public class ExploreRecommendView: ExploreRecommendAddOrEdit
{
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public DateTime? DeleteTime { get; set; }
public Guid? DeleteUserId { get; set; }
}
///<summary>ExploreRecommendQuery 列表查询参数模型</summary>
public class ExploreRecommendQuery:PageInput
{
public string? Version { get; set; }
public string? Title { get; set; }
public string? DownloadUrl { get; set; }
public string? FileName { get; set; }
public bool? IsDeleted { get; set; }
}
///<summary> ExploreRecommendAddOrEdit 列表查询参数模型</summary>
public class ExploreRecommendAddOrEdit
{
public Guid? Id { get; set; }
public string ExploreType { get; set; }
public string Version { get; set; }
public string Title { get; set; }
public bool IsDeleted { get; set; }
public string DownloadUrl { get; set; }
public string Path { get; set; }
public string FileName { get; set; }
}
}

View File

@ -0,0 +1,92 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 09:26:59
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Models;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using Microsoft.AspNetCore.Authorization;
namespace IRaCIS.Core.Application.Service
{
/// <summary>
/// ExploreRecommendService
/// </summary>
[ApiExplorerSettings(GroupName = "Common")]
public class ExploreRecommendService : BaseService, IExploreRecommendService
{
private readonly IRepository<ExploreRecommend> _exploreRecommendRepository;
public ExploreRecommendService(IRepository<ExploreRecommend> exploreRecommendRepository)
{
_exploreRecommendRepository = exploreRecommendRepository;
}
[HttpPost]
public async Task<PageOutput<ExploreRecommendView>> GetExploreRecommendList(ExploreRecommendQuery inQuery)
{
var exploreRecommendQueryable =
_exploreRecommendRepository.Where().IgnoreQueryFilters()
.WhereIf(string.IsNullOrEmpty(inQuery.Title), t => t.Title.Contains(inQuery.Title))
.WhereIf(string.IsNullOrEmpty(inQuery.FileName), t => t.Title.Contains(inQuery.FileName))
.WhereIf(string.IsNullOrEmpty(inQuery.DownloadUrl), t => t.Title.Contains(inQuery.DownloadUrl))
.WhereIf(string.IsNullOrEmpty(inQuery.Version), t => t.Title.Contains(inQuery.Version))
.WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == t.IsDeleted)
.ProjectTo<ExploreRecommendView>(_mapper.ConfigurationProvider);
var pageList = await exploreRecommendQueryable
.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, string.IsNullOrWhiteSpace(inQuery.SortField) ? nameof(ExploreRecommendView.Id) : inQuery.SortField,
inQuery.Asc);
return pageList;
}
public async Task<IResponseOutput> AddOrUpdateExploreRecommend(ExploreRecommendAddOrEdit addOrEditExploreRecommend)
{
var verifyExp2 = new EntityVerifyExp<ExploreRecommend>()
{
VerifyExp = u => u.IsDeleted == addOrEditExploreRecommend.IsDeleted && u.ExploreType == addOrEditExploreRecommend.ExploreType,
VerifyMsg = "当前浏览器启用版本只允许有一个",
IsVerify = addOrEditExploreRecommend.IsDeleted == false
};
var entity = await _exploreRecommendRepository.InsertOrUpdateAsync(addOrEditExploreRecommend, true, verifyExp2);
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{exploreRecommendId:guid}")]
public async Task<IResponseOutput> DeleteExploreRecommend(Guid exploreRecommendId)
{
var success = await _exploreRecommendRepository.DeleteFromQueryAsync(t => t.Id == exploreRecommendId, true);
return ResponseOutput.Ok();
}
[AllowAnonymous]
public async Task<List<ExploreRecommendView> > GetExploreRecommentInfo()
{
var result = await _exploreRecommendRepository.Where(t => t.IsDeleted == false).ProjectTo<ExploreRecommendView>(_mapper.ConfigurationProvider).ToListAsync();
if (result .Count==0)
{
throw new QueryBusinessObjectNotExistException("系统浏览器版本推荐未维护,请联系维护人员");
}
return result;
}
}
}

View File

@ -0,0 +1,24 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 09:27:36
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces
{
/// <summary>
/// IExploreRecommendService
/// </summary>
public interface IExploreRecommendService
{
Task<PageOutput<ExploreRecommendView>> GetExploreRecommendList(ExploreRecommendQuery inQuery);
Task<IResponseOutput> AddOrUpdateExploreRecommend(ExploreRecommendAddOrEdit addOrEditExploreRecommend);
Task<IResponseOutput> DeleteExploreRecommend(Guid exploreRecommendId);
}
}

View File

@ -80,8 +80,9 @@ namespace IRaCIS.Core.Application.Service
CreateMap<PublishLog, PublishLogAddOrEdit>().ReverseMap();
CreateMap<PublishLog, PublishVersionSelect>();
CreateMap<ExploreRecommend, ExploreRecommendView>();
CreateMap<ExploreRecommend, ExploreRecommendAddOrEdit>().ReverseMap();
}
}

View File

@ -506,6 +506,39 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
}
public async Task<IResponseOutput> GetSubejectVisitPathInfo(Guid subjectVisitId)
{
var query = from sv in _subjectVisitRepository.Where(t => t.Id == subjectVisitId)
select new
{
SubjectCode = sv.Subject.Code,
VisitName = sv.VisitName,
StudyList = sv.StudyList.Select(u => new
{
u.PatientId,
u.StudyTime,
u.StudyCode,
SeriesList = u.SeriesList.Select(z => new
{
z.Modality,
InstancePathList = z.DicomInstanceList.Select(k => new
{
k.Path
})
})
})
};
var info = query.FirstOrDefault();
return ResponseOutput.Ok(info);
}
/// <summary>
/// 后台任务调用,前端忽略该接口
/// </summary>

View File

@ -0,0 +1,66 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-03-22 15:44:37
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel
{
/// <summary> DicomAEView 列表视图模型 </summary>
public class DicomAEView : DicomAEAddOrEdit
{
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public DateTime? LatestTestTime { get; set; }
public bool IsTestOK { get; set; }
public bool IsPACSConnect { get; set; }
public bool IsTrialPACSConfirmed { get; set; }
}
///<summary>DicomAEQuery 列表查询参数模型</summary>
public class DicomAEQuery : PageInput
{
public Guid? TrialId { get; set; }
public string? CalledAE { get; set; }
public string? IP { get; set; }
public int? Port { get; set; }
public string? Modality { get; set; }
public string? Description { get; set; }
}
///<summary> DicomAEAddOrEdit 列表查询参数模型</summary>
public class DicomAEAddOrEdit
{
public Guid? Id { get; set; }
[NotDefault]
public Guid TrialId { get; set; }
public string CalledAE { get; set; }
public string IP { get; set; }
public int Port { get; set; }
public string Modality { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
}

View File

@ -313,6 +313,16 @@ namespace IRaCIS.Core.Application.Contracts
}
public class TrialPACSConfig
{
[NotDefault]
public Guid TrialId { get; set; }
public bool IsPACSConnect { get; set; }
public bool IsTrialPACSConfirmed { get; set; } = true;
}
public class TrialStateChangeDTO
{
public Guid Id { get; set; }

View File

@ -0,0 +1,60 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 16:53:52
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel
{
/// <summary> TrialSiteDicomAEView 列表视图模型 </summary>
public class TrialSiteDicomAEView : TrialSiteDicomAEAddOrEdit
{
public Guid UpdateUserId { get; set; }
public Guid? DeleteUserId { get; set; }
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public DateTime UpdateTime { get; set; }
}
///<summary>TrialSiteDicomAEQuery 列表查询参数模型</summary>
public class TrialSiteDicomAEQuery /*: PageInput*/
{
[NotDefault]
public Guid TrialSiteId { get; set; }
public string? CallingAE { get; set; }
public string? IP { get; set; }
public string? Port { get; set; }
public string? Description { get; set; }
}
///<summary> TrialSiteDicomAEAddOrEdit 列表查询参数模型</summary>
public class TrialSiteDicomAEAddOrEdit
{
public Guid? Id { get; set; }
public Guid TrialId { get; set; }
public Guid TrialSiteId { get; set; }
public string CallingAE { get; set; }
public string IP { get; set; }
public string Port { get; set; }
public string Description { get; set; }
//public bool IsDeleted { get; set; }
}
}

View File

@ -259,7 +259,7 @@ namespace IRaCIS.Application.Contracts
//public string ContactPhone { get; set; } = String.Empty;
//public string Address { get; set; } = String.Empty;
public List<string> CallingAEList { get; set; }
public List<string> UserNameList { get; set; } = new List<string>();
public int? VisitCount { get; set; }

View File

@ -0,0 +1,24 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-03-22 15:44:27
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces
{
/// <summary>
/// IDicomAEService
/// </summary>
public interface IDicomAEService
{
Task<IResponseOutput<PageOutput<DicomAEView>>> GetDicomAEList(DicomAEQuery inQuery);
Task<IResponseOutput> AddOrUpdateDicomAE(DicomAEAddOrEdit addOrEditDicomAE);
Task<IResponseOutput> DeleteDicomAE(Guid dicomAEId);
}
}

View File

@ -18,7 +18,7 @@ namespace IRaCIS.Application.Interfaces
Task<IResponseOutput> ConfigTrialUrgentInfo(TrialUrgentConfig trialConfig);
Task<IResponseOutput> ConfigTrialPACSInfo(TrialPACSConfig trialConfig);
Task<IResponseOutput> TrialConfigSignatureConfirm(SignConfirmDTO signConfirmDTO);
Task<IResponseOutput> AsyncTrialCriterionDictionary(AsyncTrialCriterionDictionaryInDto inDto);

View File

@ -0,0 +1,24 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 16:53:55
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces
{
/// <summary>
/// ITrialSiteDicomAEService
/// </summary>
public interface ITrialSiteDicomAEService
{
Task<List<TrialSiteDicomAEView>> GetTrialSiteDicomAEList(TrialSiteDicomAEQuery inQuery);
Task<IResponseOutput> AddOrUpdateTrialSiteDicomAE(TrialSiteDicomAEAddOrEdit addOrEditTrialSiteDicomAE);
Task<IResponseOutput> DeleteTrialSiteDicomAE(Guid trialSiteDicomAEId);
}
}

View File

@ -604,7 +604,7 @@ namespace IRaCIS.Core.Application
await _readingQuestionCriterionTrialRepository.UpdatePartialFromQueryAsync(inDto.TrialReadingCriterionId, x => new ReadingQuestionCriterionTrial()
{
IsImageFilter=inDto.IsImageFilter,
IsImageFilter = inDto.IsImageFilter,
ImageDownloadEnum = inDto.ImageDownloadEnum,
ImageUploadEnum = inDto.ImageUploadEnum,
CriterionModalitys = inDto.CriterionModalitys,
@ -954,7 +954,7 @@ namespace IRaCIS.Core.Application
trialInfo.UpdateTime = DateTime.Now;
//await _readingQuestionCriterionTrialRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == trialConfig.TrialId && t.IsSigned == false, u => new ReadingQuestionCriterionTrial() { CriterionModalitys = trialConfig.Modalitys });
//await _readingQuestionCriterionTrialRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == trialConfig.TrialId && t.IsSigned == false, u => new ReadingQuestionCriterionTrial() { CriterionModalitys = trialConfig.Modalitys });
return ResponseOutput.Ok(await _repository.SaveChangesAsync());
}
@ -1151,6 +1151,25 @@ namespace IRaCIS.Core.Application
return ResponseOutput.Ok(await _repository.SaveChangesAsync());
}
/// <summary>
/// 配置pacs信息
/// </summary>
/// <param name="trialConfig"></param>
/// <returns></returns>
[HttpPut]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> ConfigTrialPACSInfo(TrialPACSConfig trialConfig)
{
var trialInfo = (await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialConfig.TrialId)).IfNullThrowException();
trialInfo.IsPACSConnect = trialConfig.IsPACSConnect;
trialConfig.IsTrialPACSConfirmed = trialConfig.IsTrialPACSConfirmed;
trialInfo.UpdateTime = DateTime.Now;
await _trialRepository.SaveChangesAsync();
return ResponseOutput.Ok(await _repository.SaveChangesAsync());
}
[HttpGet("{trialId:guid}")]
public async Task<IResponseOutput> IfTrialCanOngoing(Guid trialId)
{
@ -1318,7 +1337,7 @@ namespace IRaCIS.Core.Application
public async Task<IResponseOutput<List<TrialBodyPartView>>> GetTrialBodyPartList(Guid trialId)
{
var list = await _trialRepository.Where(t => t.Id == trialId).SelectMany(t => t.TrialBodyPartList).Select(t => new TrialBodyPartView() { Code = t.Code, Name = _userInfo.IsEn_Us ? t.Name : t.NameCN ,Id=t.Id,IsHandAdd=t.IsHandAdd}).ToListAsync();
var list = await _trialRepository.Where(t => t.Id == trialId).SelectMany(t => t.TrialBodyPartList).Select(t => new TrialBodyPartView() { Code = t.Code, Name = _userInfo.IsEn_Us ? t.Name : t.NameCN, Id = t.Id, IsHandAdd = t.IsHandAdd }).ToListAsync();
return ResponseOutput.Ok(list);
}

View File

@ -0,0 +1,149 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-03-22 15:44:31
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Models;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using FellowOakDicom.Network.Client;
using FellowOakDicom.Network;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Service
{
/// <summary>
/// DicomAEService
/// </summary>
[ApiExplorerSettings(GroupName = "Trial")]
public class TrialDicomAEService : BaseService, IDicomAEService
{
private readonly IRepository<TrialDicomAE> _dicomAERepository;
private readonly IRepository<Trial> _trialRepository;
public TrialDicomAEService(IRepository<TrialDicomAE> dicomAERepository, IRepository<Trial> trialRepository)
{
_trialRepository = trialRepository;
_dicomAERepository = dicomAERepository;
}
[HttpPost]
public async Task<IResponseOutput<PageOutput<DicomAEView>>> GetDicomAEList(DicomAEQuery inQuery)
{
var dicomAEQueryable = _dicomAERepository
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.IP), t => t.IP.Contains(inQuery.IP))
.WhereIf(inQuery.Port != null, t => t.Port == inQuery.Port)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.CalledAE.Contains(inQuery.CalledAE))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Description), t => t.Description.Contains(inQuery.Description))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Modality), t => t.Modality.Contains(inQuery.Modality))
.ProjectTo<DicomAEView>(_mapper.ConfigurationProvider);
var pageList = await dicomAEQueryable.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, inQuery.SortField == string.Empty ? nameof(DicomAEView.CalledAE) : inQuery.SortField, inQuery.Asc);
return ResponseOutput.Ok(pageList);
}
/// <summary>
/// 获取项目dicom AE 配置信息otherinfo里面有IsPACSConnect IsTrialPACSConfirmed
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
public async Task<IResponseOutput<DicomAEView>> GetTrialDicomAE(Guid trialId)
{
var dicomAE = _dicomAERepository.Where(t => t.TrialId == trialId).ProjectTo<DicomAEView>(_mapper.ConfigurationProvider).FirstOrDefault();
var trialConfig = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.IsPACSConnect, t.IsTrialPACSConfirmed });
return ResponseOutput.Ok(dicomAE, trialConfig);
}
public async Task<IResponseOutput> AddOrUpdateDicomAE(DicomAEAddOrEdit addOrEditDicomAE)
{
var verifyExp1 = new EntityVerifyExp<TrialDicomAE>()
{
VerifyExp = u => u.IP == addOrEditDicomAE.IP && u.Port == addOrEditDicomAE.Port && u.TrialId == addOrEditDicomAE.TrialId,
VerifyMsg = "不允许添加相同的IP和端口的记录"
};
//var verifyExp2 = new EntityVerifyExp<TrialDicomAE>()
//{
// VerifyExp = u => u.TrialId == addOrEditDicomAE.TrialId,
// VerifyMsg = "只允许配置一条记录",
// IsVerify=addOrEditDicomAE.Id==null
//};
// 在此处拷贝automapper 映射
var entity = await _dicomAERepository.InsertOrUpdateAsync(addOrEditDicomAE, true, verifyExp1);
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{dicomAEId:guid}")]
public async Task<IResponseOutput> DeleteDicomAE(Guid dicomAEId)
{
var success = await _dicomAERepository.DeleteFromQueryAsync(t => t.Id == dicomAEId, true);
return ResponseOutput.Ok();
}
/// <summary>
/// 测试scp server 是否可以连接
/// </summary>
/// <returns></returns>
[HttpGet("{dicomAEId:guid}")]
public async Task<bool> TestSCPServerConnect(Guid dicomAEId)
{
var find = await _dicomAERepository.FirstOrDefaultAsync(t => t.Id == dicomAEId);
if (find == null)
{
return false;
}
else
{
find.LatestTestTime = DateTime.Now;
try
{
var client = DicomClientFactory.Create(find.IP, find.Port, false, "test-callingAE", find.CalledAE);
client.NegotiateAsyncOps();
await client.AddRequestAsync(new DicomCEchoRequest());
await client.SendAsync();
find.IsTestOK = true;
await _dicomAERepository.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
find.IsTestOK = false;
await _dicomAERepository.SaveChangesAsync();
return false;
}
}
}
}
}

View File

@ -0,0 +1,76 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 16:53:58
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Models;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Service
{
/// <summary>
/// TrialSiteDicomAEService
/// </summary>
[ApiExplorerSettings(GroupName = "Trial")]
public class TrialSiteDicomAEService : BaseService, ITrialSiteDicomAEService
{
private readonly IRepository<TrialSiteDicomAE> _trialSiteDicomAERepository;
public TrialSiteDicomAEService(IRepository<TrialSiteDicomAE> trialSiteDicomAERepository)
{
_trialSiteDicomAERepository = trialSiteDicomAERepository;
}
[HttpPost]
public async Task<List<TrialSiteDicomAEView>> GetTrialSiteDicomAEList(TrialSiteDicomAEQuery inQuery)
{
var trialSiteDicomAEQueryable =
_trialSiteDicomAERepository.Where(t=>t.TrialSiteId==inQuery.TrialSiteId)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.IP), t => t.IP.Contains(inQuery.IP))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Port), t => t.Port.Contains(inQuery.Port))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Description), t => t.Description.Contains(inQuery.Description))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.CallingAE.Contains(inQuery.CallingAE))
.ProjectTo<TrialSiteDicomAEView>(_mapper.ConfigurationProvider);
//var pageList = await trialSiteDicomAEQueryable
//.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, string.IsNullOrWhiteSpace(inQuery.SortField) ? nameof(TrialSiteDicomAEView.Id) : inQuery.SortField,
//inQuery.Asc);
var list = await trialSiteDicomAEQueryable.ToListAsync();
return list;
}
public async Task<IResponseOutput> AddOrUpdateTrialSiteDicomAE(TrialSiteDicomAEAddOrEdit addOrEditTrialSiteDicomAE)
{
var verifyExp1 = new EntityVerifyExp<TrialSiteDicomAE>()
{
VerifyExp = u => u.IP == addOrEditTrialSiteDicomAE.IP && u.Port == addOrEditTrialSiteDicomAE.Port &&u.CallingAE==addOrEditTrialSiteDicomAE.CallingAE && u.TrialId == addOrEditTrialSiteDicomAE.TrialId,
VerifyMsg = "不允许添加相同的IP和端口的记录"
};
var entity = await _trialSiteDicomAERepository.InsertOrUpdateAsync(addOrEditTrialSiteDicomAE, true, verifyExp1);
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{trialSiteDicomAEId:guid}")]
public async Task<IResponseOutput> DeleteTrialSiteDicomAE(Guid trialSiteDicomAEId)
{
var success = await _trialSiteDicomAERepository.DeleteFromQueryAsync(t => t.Id == trialSiteDicomAEId, true);
return ResponseOutput.Ok();
}
}
}

View File

@ -141,7 +141,8 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.UserCount, u => u.MapFrom(s => s.CRCUserList.Count()))
.ForMember(d => d.VisitCount, u => u.MapFrom(s => s.SubjectVisitList.Count()))
.ForMember(d => d.SubjectCount, u => u.MapFrom(s => s.SubjectList.Count()))
.ForMember(d => d.UserNameList, u => u.MapFrom(s => s.CRCUserList.Where(t => t.IsDeleted == false).Select(u => u.User.FullName)));
.ForMember(d => d.UserNameList, u => u.MapFrom(s => s.CRCUserList.Where(t => t.IsDeleted == false).Select(u => u.User.FullName)))
.ForMember(d => d.CallingAEList, u => u.MapFrom(s => s.TrialSiteDicomAEList.Select(u => u.CallingAE)));
//CreateMap<Site, SiteStatSimpleDTO>();

View File

@ -45,7 +45,11 @@ namespace IRaCIS.Core.Application.Service
CreateMap<AddOrUpdateTrialBodyPartCommand, TrialBodyPart>();
CreateMap<TrialSiteDicomAE, TrialSiteDicomAEView>();
CreateMap<TrialSiteDicomAE, TrialSiteDicomAEAddOrEdit>().ReverseMap();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,374 @@
using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using IRaCIS.Core.Application.Auth;
using MassTransit;
using Panda.DynamicWebApi.Attributes;
using DocumentFormat.OpenXml.Spreadsheet;
using AutoMapper.EntityFrameworkCore;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Application.Service.Reading.Dto;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SharpCompress.Common;
using System.Reactive.Subjects;
using Subject = IRaCIS.Core.Domain.Models.Subject;
using IRaCIS.Core.Application.ViewModel;
using Medallion.Threading;
using IRaCIS.Core.Infrastructure;
using EasyCaching.Core;
using Pipelines.Sockets.Unofficial.Arenas;
using IRaCIS.Core.Application.Contracts;
using MailKit.Search;
using DocumentFormat.OpenXml.Office2010.Excel;
using IRaCIS.Core.Application.Contracts.Dicom.DTO;
using IRaCIS.Core.Application.Helper;
using NPOI.SS.Formula.Functions;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text;
using DocumentFormat.OpenXml.EMMA;
using Azure;
using System.IO.Compression;
using static IRaCIS.Core.Domain.Share.StaticData;
using FellowOakDicom;
using DocumentFormat.OpenXml.Office2010.Drawing;
using EasyCaching.Core.DistributedLock;
using IDistributedLockProvider = Medallion.Threading.IDistributedLockProvider;
using DocumentFormat.OpenXml.InkML;
namespace IRaCIS.Application.Services
{
[ApiExplorerSettings(GroupName = "Trial")]
public class PatientService : BaseService
{
private readonly IRepository<Trial> _trialRepository;
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _scpStudyRepository;
private readonly IRepository<Subject> _subjectRepository;
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
public PatientService(IRepository<SCPStudy> studyRepository, IRepository<Trial> trialRepository, IRepository<SCPPatient> patientRepository, IRepository<Subject> subjectRepository, IRepository<SubjectVisit> subjectVisitRepository, IDistributedLockProvider distributedLockProvider)
{
_scpStudyRepository = studyRepository;
_trialRepository = trialRepository;
_patientRepository = patientRepository;
_subjectRepository = subjectRepository;
_subjectVisitRepository = subjectVisitRepository;
_distributedLockProvider = distributedLockProvider;
}
/// <summary>
/// scp 影像推送记录表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput<PageOutput<SCPImageUploadView>>> GetSCPImageUploadList(SCPImageUploadQuery inQuery)
{
var query = _repository.Where<SCPImageUpload>()
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.CalledAE.Contains(inQuery.CalledAE))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAEIP), t => t.CallingAEIP.Contains(inQuery.CallingAEIP))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.CallingAE.Contains(inQuery.CallingAE))
.WhereIf(inQuery.StartTime != null, t => t.StartTime >= inQuery.StartTime)
.WhereIf(inQuery.EndTime != null, t => t.EndTime <= inQuery.EndTime)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo)
|| t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo))
.ProjectTo<SCPImageUploadView>(_mapper.ConfigurationProvider);
var pageList = await query.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, inQuery.SortField == string.Empty ? nameof(SCPImageUploadView.CallingAE) : inQuery.SortField, inQuery.Asc);
return ResponseOutput.Ok(pageList);
}
#region 患者检查管理
/// <summary>
///影像检查列表-患者为维度组织
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput<PageOutput<PatientSubjectView>>> GetPatientList(PatientTrialQuery inQuery)
{
#region new ok
var query = _patientRepository
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientIdStr), t => t.PatientIdStr.Contains(inQuery.PatientIdStr))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientName), t => t.PatientName.Contains(inQuery.PatientName))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.SubejctCode), t => t.Subject.Code.Contains(inQuery.SubejctCode))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo)
|| t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo)|| t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.SCPStudyList.Any(t => t.CallingAE == inQuery.CallingAE))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.SCPStudyList.Any(t => t.CalledAE == inQuery.CalledAE))
.WhereIf(inQuery.BeginPushTime != null, t => t.LatestPushTime >= inQuery.BeginPushTime)
.WhereIf(inQuery.EndPushTime != null, t => t.LatestPushTime <= inQuery.EndPushTime);
//foreach (var calledAE in inQuery.CalledAEList)
//{
// query = query.Where(t => t.SCPStudyList.Select(c => c.CalledAE).Contains(calledAE));
//}
var resultQuery = from patient in query
select new PatientSubjectView()
{
PatientId = patient.Id,
PatientBirthDate = patient.PatientBirthDate,
CreateTime = patient.CreateTime,
CalledAEList = patient.SCPStudyList.Select(t => t.CalledAE).Distinct().ToList(),
CallingAEList = patient.SCPStudyList.Select(t => t.CallingAE).Distinct().ToList(),
CreateUserId = patient.CreateUserId,
UpdateTime = patient.UpdateTime,
UpdateUserId = patient.UpdateUserId,
EarliestStudyTime = patient.EarliestStudyTime,
LatestStudyTime = patient.LatestStudyTime,
LatestPushTime = patient.LatestPushTime,
PatientAge = patient.PatientAge,
PatientName = patient.PatientName,
PatientIdStr = patient.PatientIdStr,
PatientSex = patient.PatientSex,
StudyCount = patient.SCPStudyList.Count(),
TrialId=patient.TrialId,
SubejctId=patient.SubjectId,
SubjectCode=patient.Subject.Code,
TrialSiteAliasName=patient.TrialSite.TrialSiteAliasName,
TrialSiteCode=patient.TrialSite.TrialSiteCode,
TrialSiteName=patient.TrialSite.TrialSiteName
};
var pageList = await resultQuery.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, inQuery.SortField == string.Empty ? nameof(PatientQueryView.PatientIdStr) : inQuery.SortField, inQuery.Asc);
#endregion
return ResponseOutput.Ok(pageList);
}
/// <summary>
/// 影像检查列表-> 获取患者的检查列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<PatientStudySimpleView>> GetPatientStudyList(PatientStudyInfoQuery inQuery)
{
var query = from scpStudy in _scpStudyRepository.Where(t => t.PatientId == inQuery.PatientId)
.WhereIf(inQuery.EarliestStudyTime != null, t => t.StudyTime >= inQuery.EarliestStudyTime)
.WhereIf(inQuery.LatestStudyTime != null, t => t.StudyTime <= inQuery.LatestStudyTime)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Modalities), t => t.Modalities.Contains(inQuery.Modalities))
select new PatientStudySimpleView()
{
Description = scpStudy.Description,
CalledAE = scpStudy.CalledAE,
CallingAE = scpStudy.CallingAE,
InstanceCount = scpStudy.InstanceCount,
Modalities = scpStudy.Modalities,
PatientId = scpStudy.PatientId,
SCPStudyId = scpStudy.Id,
SeriesCount = scpStudy.SeriesCount,
StudyTime = scpStudy.StudyTime,
SubjectVisitId= scpStudy.SubjectVisitId,
VisitName=scpStudy.SubjectVisit.VisitName,
BlindName=scpStudy.SubjectVisit.BlindName
};
//var sortField = string.IsNullOrWhiteSpace(inQuery.SortField) ? nameof(PatientStudySimpleView.StudyTime) : inQuery.SortField;
//var orderQuery = inQuery.Asc ? query.OrderBy(sortField) : query.OrderBy(sortField + " desc");
//var list = await orderQuery.ToListAsync();
var pageList = await query.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, inQuery.SortField == string.Empty ? nameof(PatientStudySimpleView.StudyTime) : inQuery.SortField, inQuery.Asc);
return pageList;
}
public async Task<List<string>> GetDicomCalledAEList()
{
var list = await _scpStudyRepository.Select(t => t.CalledAE).Distinct().ToListAsync();
return list;
}
public async Task<List<string>> GetDicomCallingAEList()
{
var list = await _scpStudyRepository.Select(t => t.CallingAE).Distinct().ToListAsync();
return list;
}
/// <summary>
/// 影像访视上传 检查列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<VisitPatientStudyFilterView>> GetVisitPatientStudyFilterList(VisitPatientStudyFilterQuery inQuery)
{
var trialSiteId=_subjectRepository.Where(t=>t.Id==inQuery.SubjectId).Select(t=>t.TrialSiteId).FirstOrDefault();
var query = from scpStudy in _scpStudyRepository
//未绑定的患者,或者自己已绑定但是未绑定访视的
.Where(t => t.Patient.SubjectId == null|| (t.Patient.SubjectId == inQuery.SubjectId && t.SubjectVisitId==null))
//中心
.Where(t=>t.TrialSiteId==trialSiteId)
.WhereIf(inQuery.EarliestStudyTime != null, t => t.StudyTime >= inQuery.EarliestStudyTime)
.WhereIf(inQuery.LatestStudyTime != null, t => t.StudyTime <= inQuery.LatestStudyTime)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.Modalities), t => t.Modalities.Contains(inQuery.Modalities))
select new VisitPatientStudyFilterView()
{
Description = scpStudy.Description,
CalledAE = scpStudy.CalledAE,
CallingAE = scpStudy.CallingAE,
InstanceCount = scpStudy.InstanceCount,
Modalities = scpStudy.Modalities,
PatientId = scpStudy.PatientId,
SCPStudyId = scpStudy.Id,
SeriesCount = scpStudy.SeriesCount,
StudyTime = scpStudy.StudyTime,
};
var pageList = await query.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, inQuery.SortField == string.Empty ? nameof(PatientStudySimpleView.StudyTime) : inQuery.SortField, inQuery.Asc);
return pageList;
}
/// <summary>
/// 提交 患者检查和访视的绑定
/// </summary>
/// <param name="inCommand"></param>
/// <returns></returns>
[HttpPost]
[UnitOfWork]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> SubmitVisitStudyBinding(SubmitVisitStudyBindingCommand inCommand)
{
var subjectId = inCommand.SubjectId;
var subjectVisitId=inCommand.SubjectVisitId;
var trialId = inCommand.TrialId;
var @lock = _distributedLockProvider.CreateLock($"StudyCode");
using (await @lock.AcquireAsync())
{
var dbStudyCodeIntMax = _repository.Where<DicomStudy>(s => s.TrialId == inCommand.TrialId).Select(t => t.Code).DefaultIfEmpty().Max();
int currentNextCodeInt = dbStudyCodeIntMax + 1;
foreach (var scpStudyId in inCommand.SCPStudyIdList)
{
var find = _scpStudyRepository.Where(t => t.Id == scpStudyId).Include(t => t.SeriesList).Include(t => t.InstanceList).FirstOrDefault();
if (find != null)
{
var newStuty = _mapper.Map<DicomStudy>(find);
await _repository.AddAsync(newStuty);
newStuty.SeqId = Guid.Empty;
newStuty.Code = currentNextCodeInt;
newStuty.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy));
newStuty.IsFromPACS = true;
newStuty.TrialId = trialId;
newStuty.SubjectId = subjectId;
newStuty.SubjectVisitId = subjectVisitId;
var newSeriesList = _mapper.Map<List<DicomSeries>>(find.SeriesList);
foreach (var series in newSeriesList)
{
series.SeqId = Guid.Empty;
series.TrialId = trialId;
series.SubjectId = subjectId;
series.SubjectVisitId = subjectVisitId;
}
await _repository.AddRangeAsync(newSeriesList);
var newInstanceList = _mapper.Map<List<DicomInstance>>(find.InstanceList);
foreach (var instance in newInstanceList)
{
instance.SeqId = Guid.Empty;
instance.TrialId = trialId;
instance.SubjectId = subjectId;
instance.SubjectVisitId = subjectVisitId;
}
await _repository.AddRangeAsync(newInstanceList);
}
currentNextCodeInt++;
}
}
await _repository.SaveChangesAsync();
return ResponseOutput.Ok();
}
#endregion
}
}

View File

@ -112,6 +112,14 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.InstanceInfoList, u => u.MapFrom(s => s.InstanceList));
CreateMap<TaskInstance, InstanceBasicInfo>();
CreateMap<SCPImageUpload, SCPImageUploadView>()
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.TrialSite.TrialSiteCode))
.ForMember(d => d.TrialSiteAliasName, u => u.MapFrom(s => s.TrialSite.TrialSiteAliasName))
.ForMember(d => d.TrialSiteName, u => u.MapFrom(s => s.TrialSite.TrialSiteName))
;
}
}

View File

@ -213,7 +213,7 @@ namespace IRaCIS.Core.Application.MediatR.Handlers
else
{
//"Problems are as follows:
dialogMsg.AppendLine($"<br/><div style='color: yellow'>{_localizer["ConsistencyVerification_Prob"]}</div>");
dialogMsg.AppendLine($"<br/><div style='color: red'>{_localizer["ConsistencyVerification_Prob"]}</div>");
num = 0;
foreach (var item in dbExceptExcel)

View File

@ -0,0 +1,55 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 09:26:43
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
using System;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
///<summary>
///ExploreRecommend
///</summary>
[Table("ExploreRecommend")]
public class ExploreRecommend : Entity, IAuditUpdate, IAuditAdd,ISoftDelete
{
public string ExploreType { get; set; } = string.Empty;
public string Version { get; set; }=string.Empty;
public string Title { get; set; } = string.Empty;
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public bool IsDeleted { get; set; }
public string DownloadUrl { get; set; } = string.Empty;
public string Path { get; set; } = string.Empty;
public string FileName { get; set; } = string.Empty;
public DateTime? DeletedTime { get; set; }
public Guid? DeleteUserId { get; set; }
}
}

View File

@ -99,5 +99,8 @@ namespace IRaCIS.Core.Domain.Models
public Guid? DeleteUserId { get; set; }
public bool IsFromPACS { get; set; }
}
}

View File

@ -0,0 +1,74 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-05-24 14:31:45
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
using System;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Collections.Generic;
namespace IRaCIS.Core.Domain.Models
{
///<summary>
///SCPImageUpload
///</summary>
[Table("SCPImageUpload")]
public class SCPImageUpload : Entity, IAuditAdd
{
[Required]
public DateTime CreateTime { get; set; }
[Required]
public Guid CreateUserId { get; set; }
[Required]
public string CallingAE { get; set; }=string.Empty;
[Required]
public string CalledAE { get; set; } = string.Empty;
[Required]
public string CallingAEIP { get; set; } = string.Empty;
[Required]
public DateTime StartTime { get; set; }
[Required]
public DateTime EndTime { get; set; }
[Required]
public int FileCount { get; set; }
[Required]
public long FileSize { get; set; }
public int StudyCount { get; set; }
public Guid TrialId { get; set; }
public Guid TrialSiteId { get; set; }
[JsonIgnore]
public Trial Trial { get; set; }
[JsonIgnore]
public TrialSite TrialSite { get; set; }
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
[Table("SCPInstance")]
public class SCPInstance : Entity, IAuditAdd, IAuditUpdate
{
[JsonIgnore]
[ForeignKey("SeriesId")]
public SCPSeries SCPSeries { get; set; }
[JsonIgnore]
[ForeignKey("StudyId")]
public SCPStudy SCPStudy { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid SeqId { get; set; }
public Guid StudyId { get; set; }
public Guid SeriesId { get; set; }
public string StudyInstanceUid { get; set; }
public string SeriesInstanceUid { get; set; }
public string SopInstanceUid { get; set; }
public int InstanceNumber { get; set; }
public DateTime? InstanceTime { get; set; }
public bool CPIStatus { get; set; }
public int ImageRows { get; set; }
public int ImageColumns { get; set; }
public int SliceLocation { get; set; }
public string SliceThickness { get; set; }
public int NumberOfFrames { get; set; }
public string PixelSpacing { get; set; }
public string ImagerPixelSpacing { get; set; }
public string FrameOfReferenceUID { get; set; }
public string WindowCenter { get; set; }
public string WindowWidth { get; set; }
public bool Anonymize { get; set; }
public string Path { get; set; } = string.Empty;
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; } = DateTime.Now;
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
[Table("SCPPatient")]
public class SCPPatient : Entity, IAuditUpdate, IAuditAdd
{
public List<SCPStudy> SCPStudyList { get; set; }
public string PatientIdStr { get; set; } = string.Empty;
public string PatientName { get; set; } = string.Empty;
public string PatientAge { get; set; } = string.Empty;
public string PatientSex { get; set; } = string.Empty;
public string PatientBirthDate { get; set; } = string.Empty;
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; } = DateTime.Now;
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
public DateTime? EarliestStudyTime { get; set; }
public DateTime? LatestStudyTime { get; set; }
public DateTime LatestPushTime { get; set; }
public Guid? SubjectId { get; set; }
public Guid TrialId { get; set; }
public Guid TrialSiteId { get; set; }
[JsonIgnore]
public Subject Subject { get; set; }
[JsonIgnore]
public Trial Trial { get; set; }
[JsonIgnore]
public TrialSite TrialSite { get; set; }
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
[Table("SCPSeries")]
public class SCPSeries : Entity, IAuditAdd, IAuditUpdate, ISoftDelete
{
[JsonIgnore]
[ForeignKey("StudyId")]
public SCPStudy SCPStudy { get; set; }
[JsonIgnore]
public List<SCPInstance> SCPInstanceList { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid SeqId { get; set; }
public Guid StudyId { get; set; }
public string StudyInstanceUid { get; set; }
public string SeriesInstanceUid { get; set; }
public int SeriesNumber { get; set; }
public DateTime? SeriesTime { get; set; }
public string Modality { get; set; }
public string Description { get; set; }
public int InstanceCount { get; set; }
public string SliceThickness { get; set; }
public string ImagePositionPatient { get; set; }
public string ImageOrientationPatient { get; set; }
public string BodyPartExamined { get; set; }
public string SequenceName { get; set; }
public string ProtocolName { get; set; }
public string ImagerPixelSpacing { get; set; }
public string AcquisitionTime { get; set; } = string.Empty;
public string AcquisitionNumber { get; set; } = string.Empty;
public string TriggerTime { get; set; } = string.Empty;
public string BodyPartForEdit { get; set; } = string.Empty;
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; } = DateTime.Now;
public DateTime? DeletedTime { get; set; }
public Guid? DeleteUserId { get; set; }
public bool IsDeleted {get;set;}
public bool IsReading { get; set; } = true;
public string ImageResizePath { get; set; }=string.Empty;
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
[Table("SCPStudy")]
public class SCPStudy : Entity, IAuditUpdate, IAuditAdd, ISoftDelete
{
//0 未知 1 单重 2 双重
public bool IsDoubleReview { get; set; }
[JsonIgnore]
public List<SCPInstance> InstanceList { get; set; }
[JsonIgnore]
public List<SCPSeries> SeriesList { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid SeqId { get; set; }
[ForeignKey("PatientId")]
[JsonIgnore]
public SCPPatient Patient { get; set; }
public Guid PatientId { get; set; }
public string StudyInstanceUid { get; set; } = string.Empty;
public DateTime? StudyTime { get; set; }
public string Modalities { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int SeriesCount { get; set; } = 0;
public int InstanceCount { get; set; } = 0;
public string InstitutionName { get; set; } = string.Empty;
public string PatientIdStr { get; set; } = string.Empty;
public string PatientName { get; set; } = string.Empty;
public string PatientAge { get; set; } = string.Empty;
public string PatientSex { get; set; } = string.Empty;
public string StudyId { get; set; } = string.Empty;
public string AccessionNumber { get; set; } = string.Empty;
public string PatientBirthDate { get; set; } = string.Empty;
public string AcquisitionTime { get; set; } = string.Empty;
public string AcquisitionNumber { get; set; } = string.Empty;
public string TriggerTime { get; set; } = string.Empty;
public string BodyPartExamined { get; set; } = string.Empty;
public string BodyPartForEdit { get; set; } = string.Empty;
public string ModalityForEdit { get; set; } = string.Empty;
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; } = DateTime.Now;
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
//软删除
public bool IsDeleted { get; set; }
public DateTime? DeletedTime { get; set; }
public Guid? DeleteUserId { get; set; }
public string CallingAE { get; set; } = string.Empty;
public string CalledAE { get; set; } = string.Empty;
public bool IsUploadFinished { get; set; }
public Guid TrialId { get; set; }
public Guid TrialSiteId { get; set; }
public Guid? SubjectVisitId { get; set; }
[JsonIgnore]
public SubjectVisit SubjectVisit { get; set; }
[JsonIgnore]
public Trial Trial { get; set; }
[JsonIgnore]
public TrialSite TrialSite { get; set; }
}
}

View File

@ -0,0 +1,52 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-03-22 15:44:11
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
using System;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
///<summary>
///DicomAE
///</summary>
[Table("TrialDicomAE")]
public class TrialDicomAE : Entity, IAuditUpdate, IAuditAdd
{
public Guid TrialId { get; set; }
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public string CalledAE { get; set; } = string.Empty;
public string IP { get; set; }
public int Port { get; set; }
public string Modality { get; set; } = string.Empty;
public string Description { get; set; }=string.Empty;
public DateTime? LatestTestTime { get; set; }
public bool IsTestOK { get; set; }
}
}

View File

@ -401,7 +401,9 @@ namespace IRaCIS.Core.Domain.Models
#endregion
public bool IsPACSConnect { get; set; }
public bool IsTrialPACSConfirmed { get; set; }
///// <summary>
///// 图像是否有标注

View File

@ -55,7 +55,9 @@ namespace IRaCIS.Core.Domain.Models
[JsonIgnore]
public List<Subject> SubjectList { get; set; }
[JsonIgnore]
public List<TrialSiteDicomAE> TrialSiteDicomAEList { get; set; }
}
}

View File

@ -0,0 +1,63 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2024-07-02 16:53:49
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
using System;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
///<summary>
///TrialSiteDicomAE
///</summary>
[Table("TrialSiteDicomAE")]
public class TrialSiteDicomAE : Entity, IAuditUpdate, IAuditAdd
{
public DateTime? DeletedTime { get; set; }
public Guid TrialId { get; set; }
public Guid UpdateUserId { get; set; }
public Guid? DeleteUserId { get; set; }
public DateTime CreateTime { get; set; }
public Guid CreateUserId { get; set; }
public bool IsDeleted { get; set; }
public DateTime UpdateTime { get; set; }
public Guid TrialSiteId { get; set; }
public string CallingAE { get; set; }
public string IP { get; set; }
public string Port { get; set; }
public string Description { get; set; }
public TrialSite TrialSite { get; set; }
}
}

View File

@ -481,6 +481,21 @@ namespace IRaCIS.Core.Infra.EFCore
public virtual DbSet<TrialBodyPart> TrialBodyPart { get; set; }
public virtual DbSet<ExploreRecommend> ExploreRecommend { get; set; }
public virtual DbSet<SCPPatient> SCPPatient { get; set; }
public virtual DbSet<SCPStudy> SCPStudy { get; set; }
public virtual DbSet<SCPSeries> SCPSeries { get; set; }
public virtual DbSet<SCPInstance> SCPInstance { get; set; }
public virtual DbSet<TrialDicomAE> TrialDicomAE { get; set; }
public virtual DbSet<TrialSiteDicomAE> TrialSiteDicomAE { get; set; }
public virtual DbSet<SCPImageUpload> SCPImageUpload { get; set; }
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
// 采用触发器的方式 设置 CreateUserId CreateTime UpdateTime UpdateUserId 稽查实体里面没有这四个字段的值 因为先后顺序的原因

View File

@ -9,13 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Collection.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="AutoMapper.Collection.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="SharpCompress" Version="0.35.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="SharpCompress" Version="0.37.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.3" />
</ItemGroup>

View File

@ -4,7 +4,7 @@
public static readonly string ConnectionString = "Server=106.14.89.110,1435;Database=Test_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true";
public static readonly string DbDatabase = "Test_IRC";
//表名称用字符串,拼接
public static readonly string TableName = "TrialBodyPart";
public static readonly string TableName = "TrialSiteDicomAE";
//具体文件里面 例如service 可以配置是否分页
}
#>

View File

@ -65,6 +65,7 @@ namespace IRaCIS.Core.Application.Service
}
<# if(isPage){#>
[HttpPost]
public async Task<PageOutput<<#=tableName#>View>> Get<#=tableName#>List(<#=tableName#>Query inQuery)
{
@ -74,13 +75,13 @@ namespace IRaCIS.Core.Application.Service
.ProjectTo<<#=tableName#>View>(_mapper.ConfigurationProvider);
var pageList= await <#=char.ToLower(tableName[0]) + tableName.Substring(1)#>Queryable
.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, string.IsNullOrWhiteSpace(inQuery.SortField) ? "Id" : inQuery.SortField,
.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, string.IsNullOrWhiteSpace(inQuery.SortField) ? nameof(<#=tableName#>View.Id) : inQuery.SortField,
inQuery.Asc);
return pageList;
}
<# } else {#>
[HttpPost]
public async Task<List<<#=tableName#>View>> Get<#=tableName#>List(<#=tableName#>Query inQuery)
{