Compare commits

..

No commits in common. "Test_IRC_Net8" and "EICS-V1.7.0" have entirely different histories.

545 changed files with 22180 additions and 418863 deletions

View File

@ -1,37 +0,0 @@
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
namespace IRaCIS.Core.SCP.Filter
{
public class ModelActionFilter : ActionFilterAttribute, IActionFilter
{
public IStringLocalizer _localizer;
public ModelActionFilter(IStringLocalizer localizer)
{
_localizer = localizer;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k]!.Errors)
.Select(e => e.ErrorMessage)
.ToArray();
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject( validationErrors)));
}
}
}
}

View File

@ -1,61 +0,0 @@
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.SCP.Filter
{
public class ProjectExceptionFilter : Attribute, IExceptionFilter
{
private readonly ILogger<ProjectExceptionFilter> _logger;
public IStringLocalizer _localizer;
public ProjectExceptionFilter(IStringLocalizer localizer, ILogger<ProjectExceptionFilter> logger)
{
_logger = logger;
_localizer = localizer;
}
public void OnException(ExceptionContext context)
{
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
if (!context.ExceptionHandled)
{
if (context.Exception.GetType().Name == "DbUpdateConcurrencyException")
{
//---并发更新,当前不允许该操作
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + context.Exception.Message));
}
if (context.Exception.GetType() == typeof(BusinessValidationFailedException))
{
var error = context.Exception as BusinessValidationFailedException;
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, error!.Code));
}
else if(context.Exception.GetType() == typeof(QueryBusinessObjectNotExistException))
{
context.Result = new JsonResult(ResponseOutput.NotOk( context.Exception.Message, ApiResponseCodeEnum.DataNotExist));
}
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (context.Exception.InnerException is null ? (context.Exception.Message /*+ context.Exception.StackTrace*/)
: (context.Exception.InnerException?.Message /*+ context.Exception.InnerException?.StackTrace*/)), ApiResponseCodeEnum.ProgramException));
}
_logger.LogError(context.Exception.InnerException is null ? (context.Exception.Message + context.Exception.StackTrace) : (context.Exception.InnerException?.Message + context.Exception.InnerException?.StackTrace));
}
else
{
//继续
}
context.ExceptionHandled = true;//标记当前异常已经被处理过了
}
}
}

View File

@ -1,120 +0,0 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Core.Application.Service.BusinessFilter
{
/// <summary>
/// 统一返回前端数据包装之前在控制器包装现在修改为动态Api 在ResultFilter这里包装减少重复冗余代码
/// by zhouhang 2021.09.12 周末
/// </summary>
public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
{
/// <summary>
/// 异步版本
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is ObjectResult objectResult)
{
var statusCode = objectResult.StatusCode ?? context.HttpContext.Response.StatusCode;
//是200 并且没有包装 那么包装结果
if (statusCode == 200 && !(objectResult.Value is IResponseOutput))
{
//if (objectResult.Value == null)
//{
// var apiResponse = ResponseOutput.DBNotExist();
// objectResult.Value = apiResponse;
// objectResult.DeclaredType = apiResponse.GetType();
//}
//else
//{
var type = objectResult.Value?.GetType();
if ( type!=null&& type.IsGenericType&&(type.GetGenericTypeDefinition()==typeof(ValueTuple<,>)|| type.GetGenericTypeDefinition()==typeof(Tuple<,>)))
{
//报错
//var tuple = (object, object))objectResult.Value;
//var (val1, val2) = ((dynamic, dynamic))objectResult.Value;
//var apiResponse = ResponseOutput.Ok(val1, val2);
//OK
var tuple = (dynamic)objectResult.Value;
var apiResponse = ResponseOutput.Ok(tuple.Item1, tuple.Item2);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
else
{
var apiResponse = ResponseOutput.Ok(objectResult.Value);
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
//}
}
//如果不是200 是IResponseOutput 不处理
else if (statusCode != 200 && (objectResult.Value is IResponseOutput))
{
}
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
{
//---程序错误,请联系开发人员。
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType();
}
}
await next.Invoke();
}
public static bool IsTupleType(Type type, bool checkBaseTypes = false)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type == typeof(Tuple))
return true;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(Tuple<>)
|| genType == typeof(Tuple<,>)
|| genType == typeof(Tuple<,>))
return true;
}
if (!checkBaseTypes)
break;
type = type.BaseType;
}
return false;
}
}
}

View File

@ -1,64 +0,0 @@
using EntityFramework.Exceptions.SqlServer;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using Medallion.Threading.SqlServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.SCP
{
public static class EFSetup
{
public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration)
{
services.AddHttpContextAccessor();
services.AddScoped<IUserInfo, UserInfo>();
services.AddScoped<ISaveChangesInterceptor, AuditEntityInterceptor>();
//这个注入没有成功--注入是没问题的构造函数也只是支持参数就好错在注入的地方不能写DbContext
//Web程序中通过重用池中DbContext实例可提高高并发场景下的吞吐量 这在概念上类似于ADO.NET Provider原生的连接池操作方式具有节省DbContext实例化成本的优点
services.AddDbContext<IRaCISDBContext>((sp, options) =>
{
// 在控制台
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value,
contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseLoggerFactory(logFactory);
options.UseExceptionProcessor();
options.EnableSensitiveDataLogging();
options.AddInterceptors(new QueryWithNoLockDbCommandInterceptor());
options.AddInterceptors(sp.GetServices<ISaveChangesInterceptor>());
options.UseProjectables();
});
//// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
//services.AddScoped<IRaCISDBScopedFactory>();
//// Finally, arrange for a context to get injected from our Scoped factory:
//services.AddScoped(sp => sp.GetRequiredService<IRaCISDBScopedFactory>().CreateDbContext());
//注意区分 easy caching 也有 IDistributedLockProvider
services.AddSingleton<IDistributedLockProvider>(sp =>
{
//var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
return new SqlDistributedSynchronizationProvider(configuration.GetSection("ConnectionStrings:RemoteNew").Value);
});
}
}
}

View File

@ -1,59 +0,0 @@

using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace IRaCIS.Core.SCP
{
public static class NewtonsoftJsonSetup
{
public static void AddNewtonsoftJsonSetup(this IMvcBuilder builder, IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IOSSService,OSSService>();
builder.AddNewtonsoftJson(options =>
{
//options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// 忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
//处理返回给前端 可空类型 给出默认值 比如in? 为null 设置 默认值0
options.SerializerSettings.ContractResolver = new NullToEmptyStringResolver(); //new DefaultContractResolver();// new NullToEmptyStringResolver();
// 设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
//options.SerializerSettings.Converters.Add(new JSONCustomDateConverter()) ;
//options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
})
.AddControllersAsServices()//动态webApi属性注入需要
.ConfigureApiBehaviorOptions(o =>
{
o.SuppressModelStateInvalidFilter = true; //自己写验证
});
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
{
//日期类型默认格式化处理
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return setting;
});
}
}
}

View File

@ -1,36 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
namespace IRaCIS.Core.SCP
{
public class NullToEmptyStringResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
var list= type.GetProperties()
.Select(p =>
{
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new NullToEmptyStringValueProvider(p);
return jp;
}).ToList();
var uu = list.Select(t => t.PropertyName).ToList();
//获取复杂对象属性
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
list.AddRange(properties);
return list;
}
}
}

View File

@ -1,45 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IRaCIS.Core.Infra.EFCore\IRaCIS.Core.Infra.EFCore.csproj" />
<ProjectReference Include="..\IRaCIS.Core.Infrastructure\IRaCIS.Core.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Helper\" />
</ItemGroup>
</Project>

View File

@ -1,186 +0,0 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.Imaging.NativeCodec;
using FellowOakDicom.Network;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.SCP;
using IRaCIS.Core.SCP.Filter;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using MassTransit.NewIdProviders;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Panda.DynamicWebApi;
using Serilog;
using Serilog.Events;
using System.Runtime.InteropServices;
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = enviromentName
});
#region 主机配置
NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
builder.Configuration.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{enviromentName}.json", false, true);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<AutofacModuleSetup>();
})
.UseSerilog();
#endregion
#region 配置服务
var _configuration = builder.Configuration;
//健康检查
builder.Services.AddHealthChecks();
//本地化
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
builder.Services.AddOptions().Configure<DicomSCPServiceOption>(_configuration.GetSection("DicomSCPServiceConfig"));
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
//动态webApi 目前存在的唯一小坑是生成api上服务上的动态代理AOP失效 间接掉用不影响
builder.Services
.AddDynamicWebApi(dynamicWebApiOption =>
{
//默认是 api
dynamicWebApiOption.DefaultApiPrefix = "";
//首字母小写
dynamicWebApiOption.GetRestFulActionName = (actionName) => char.ToLower(actionName[0]) + actionName.Substring(1);
//删除 Service后缀
dynamicWebApiOption.RemoveControllerPostfixes.Add("Service");
});
//AutoMapper
builder.Services.AddAutoMapper(automapper =>
{
automapper.AddCollectionMappers();
}, typeof(BaseService).Assembly);
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
builder.Services.AddMediator(cfg =>
{
});
//转发头设置 获取真实IP
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>();
#endregion
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.UseAuthorization();
app.MapControllers();
#region 日志
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
#endregion
#region 运行环境 部署平台
Log.Logger.Warning($"当前环境:{enviromentName}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Log.Logger.Warning($"当前部署平台环境windows");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Log.Logger.Warning($"当前部署平台环境linux");
}
else
{
Log.Logger.Warning($"当前部署平台环境OSX or FreeBSD");
}
#endregion
DicomSetupBuilder.UseServiceProvider(app.Services);
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services, logger: logger);
app.Run();

View File

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

View File

@ -1,111 +0,0 @@
using AutoMapper;
using IRaCIS.Core.Application.Service.BusinessFilter;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Localization;
using Panda.DynamicWebApi;
using Panda.DynamicWebApi.Attributes;
using System.Diagnostics.CodeAnalysis;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.Domain.Models;
namespace IRaCIS.Core.SCP.Service
{
#pragma warning disable CS8618
#region 非泛型版本
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseService : IBaseService, IDynamicWebApi
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public IWebHostEnvironment _hostEnvironment { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
public interface IBaseService
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
[MemberNotNull(nameof(_hostEnvironment))]
public IWebHostEnvironment _hostEnvironment { get; set; }
}
#endregion
#region 泛型版本测试
public interface IBaseServiceTest<T> where T : Entity
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
}
[Authorize, DynamicWebApi, UnifiedApiResultFilter]
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
#endregion
}

View File

@ -1,376 +0,0 @@
using FellowOakDicom.Network;
using FellowOakDicom;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using IRaCIS.Core.Domain.Share;
using Serilog;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Options;
using System.Data;
using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Core.SCP.Service
{
public class DicomSCPServiceOption
{
public List<string> CalledAEList { get; set; }
public string ServerPort { get; set; }
}
public class CStoreSCPService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
private IServiceProvider _serviceProvider { get; set; }
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
private SCPImageUpload _upload { get; set; }
private Guid _trialId { get; set; }
private Guid _trialSiteId { get; set; }
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
{
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
{
// Lossless
DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80
DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90
DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70
DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14)
DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5
// Lossy
DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81"
DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91
DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50
DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51
// Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1
DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2
DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2
};
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider)
: base(stream, fallbackEncoding, log, dependencies)
{
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
}
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
{
_upload = new SCPImageUpload() { StartTime = DateTime.Now, CallingAE = association.CallingAE, CalledAE = association.CalledAE, CallingAEIP = association.RemoteHost };
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
//_serviceProvider = (IServiceProvider)this.UserState;
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList();
var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList();
Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList));
var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault();
var isCanReceiveIamge = false;
if (findCalledAE != null)
{
_trialId = findCalledAE.TrialId;
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
_trialSiteId = findTrialSiteAE.TrialSiteId;
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
{
isCanReceiveIamge = true;
}
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false)
{
Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接");
return SendAssociationRejectAsync(
DicomRejectResult.Permanent,
DicomRejectSource.ServiceUser,
DicomRejectReason.CalledAENotRecognized);
}
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification)
{
pc.AcceptTransferSyntaxes(_acceptedTransferSyntaxes);
}
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
{
pc.AcceptTransferSyntaxes(_acceptedImageTransferSyntaxes);
}
}
return SendAssociationAcceptAsync(association);
}
public async Task OnReceiveAssociationReleaseRequestAsync()
{
await DataMaintenanceAsaync();
//记录监控
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
await _SCPImageUploadRepository.AddAsync(_upload, true);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
private async Task DataMaintenanceAsaync()
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束开始维护数据处理检查Modality");
//处理检查Modality
var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList();
var seriesModalityList = _seriesRepository.Where(t => _SCPStudyIdList.Contains(t.StudyId)).Select(t => new { SCPStudyId = t.StudyId, t.Modality }).ToList();
foreach (var g in seriesModalityList.GroupBy(t => t.SCPStudyId))
{
var modality = string.Join('、', g.Select(t => t.Modality).Distinct().ToList());
//特殊逻辑
var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty;
if (modality == "MR")
{
modalityForEdit = "MRI";
}
if (modality == "PT")
{
modalityForEdit = "PET";
}
if (modality == "PT、CT" || modality == "CT、PT")
{
modalityForEdit = "PET-CT";
}
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == g.Key, u => new SCPStudy() { Modalities = modality, ModalityForEdit = modalityForEdit });
}
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}维护数据结束");
}
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}接收中断,中断原因:{source.ToString() + reason.ToString()}");
/* nothing to do here */
}
public async void OnConnectionClosed(Exception exception)
{
/* nothing to do here */
//奇怪的bug 上传的时候用王捷修改的影像会关闭重新连接导致检查id 丢失,然后状态不一致
if (exception == null)
{
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
{
string studyInstanceUid = request.Dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
var ossService = _serviceProvider.GetService<IOSSService>();
var dicomArchiveService = _serviceProvider.GetService<IDicomArchiveService>();
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
long fileSize = 0;
try
{
using (MemoryStream ms = new MemoryStream())
{
await request.File.SaveAsync(ms);
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
}
catch (Exception ec)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
}
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(request.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
Console.WriteLine(seriesPath + " Id: " + seriesId);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
public Task OnCStoreRequestExceptionAsync(string tempFileName, Exception e)
{
// let library handle logging and error response
return Task.CompletedTask;
}
public Task<DicomCEchoResponse> OnCEchoRequestAsync(DicomCEchoRequest request)
{
return Task.FromResult(new DicomCEchoResponse(request, DicomStatus.Success));
}
}
}

View File

@ -1,356 +0,0 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService : BaseService, IDicomArchiveService
{
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<SCPSeries> _seriesRepository;
private readonly IRepository<SCPInstance> _instanceRepository;
private readonly IRepository<Dictionary> _dictionaryRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
private List<Guid> _instanceIdList = new List<Guid>();
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
IRepository<SCPSeries> seriesRepository,
IRepository<SCPInstance> instanceRepository,
IRepository<Dictionary> dictionaryRepository,
IDistributedLockProvider distributedLockProvider)
{
_distributedLockProvider = distributedLockProvider;
_studyRepository = studyRepository;
_patientRepository = patientRepository;
_seriesRepository = seriesRepository;
_instanceRepository = instanceRepository;
_dictionaryRepository = dictionaryRepository;
}
/// <summary>
/// 单个文件接收 归档
/// </summary>
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
{
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
var isStudyNeedAdd = false;
var isSeriesNeedAdd = false;
var isInstanceNeedAdd = false;
var isPatientNeedAdd = false;
//var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
//using (@lock.Acquire())
{
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的又传输了没有修改患者编号的导致后传输的没有修改患者编号的下面的检查为0
if (findPatient == null && findStudy==null)
{
isPatientNeedAdd = true;
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId=trialId,
TrialSiteId=trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
EarliestStudyTime = studyTime,
LatestStudyTime = studyTime,
LatestPushTime = DateTime.Now,
};
if (findPatient.PatientBirthDate.Length == 8)
{
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
int year = 0;
var canParse = int.TryParse(yearStr, out year);
if (canParse && year > 1900)
{
findPatient.PatientBirthDate = birthDateStr;
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
if (studyTime.Value < birthDate.AddYears(patientAge))
{
patientAge--;
}
findPatient.PatientAge = patientAge.ToString();
}
}
else
{
findPatient.PatientBirthDate = string.Empty;
}
}
}
else
{
if (studyTime < findPatient.EarliestStudyTime)
{
findPatient.EarliestStudyTime = studyTime;
}
if (studyTime > findPatient.LatestStudyTime)
{
findPatient.LatestStudyTime = studyTime;
}
findPatient.LatestPushTime = DateTime.Now;
}
if (findStudy == null)
{
isStudyNeedAdd = true;
findStudy = new SCPStudy
{
CalledAE = calledAE,
CallingAE = callingAE,
PatientId = findPatient.Id,
Id = studyId,
TrialId = trialId,
TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime,
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
//ModalityForEdit = modalityForEdit,
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
InstitutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty),
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
StudyId = dataset.GetSingleValueOrDefault(DicomTag.StudyID, string.Empty),
AccessionNumber = dataset.GetSingleValueOrDefault(DicomTag.AccessionNumber, string.Empty),
//需要特殊处理
PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
//IsDoubleReview = addtionalInfo.IsDoubleReview,
SeriesCount = 0,
InstanceCount = 0
};
if (findStudy.PatientBirthDate.Length == 8)
{
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
}
}
if (findSerice == null)
{
isSeriesNeedAdd = true;
findSerice = new SCPSeries
{
Id = seriesId,
StudyId = findStudy.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = seriesInstanceUid,
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
InstanceCount = 0
};
++findStudy.SeriesCount;
}
if (findInstance == null)
{
isInstanceNeedAdd = true;
findInstance = new SCPInstance
{
Id = instanceId,
StudyId = findStudy.Id,
SeriesId = findSerice.Id,
StudyInstanceUid = findStudy.StudyInstanceUid,
SeriesInstanceUid = findSerice.SeriesInstanceUid,
SopInstanceUid = sopInstanceUid,
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
//InstanceTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.ContentDate) + dataset.GetSingleValue<string>(DicomTag.ContentTime), out DateTime dt) ? dt : null,
//InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate,(DateTime?)null)?.Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, TimeSpan.Zero)),
//dataset.GetSingleValueOrDefault(DicomTag.ContentDate,DateTime.Now);//, DicomTag.ContentTime)
CPIStatus = false,
ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
Path = fileRelativePath,
FileSize= fileSize,
};
++findStudy.InstanceCount;
++findSerice.InstanceCount;
}
if (isPatientNeedAdd)
{
var ss = await _patientRepository.AddAsync(findPatient);
}
if (isStudyNeedAdd)
{
var dd = await _studyRepository.AddAsync(findStudy);
}
else
{
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.Id, t => new SCPStudy() { IsUploadFinished = false });
}
if (isSeriesNeedAdd)
{
await _seriesRepository.AddAsync(findSerice);
}
if (isInstanceNeedAdd)
{
await _instanceRepository.AddAsync(findInstance);
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
}
await _studyRepository.SaveChangesAsync();
return findStudy.Id;
}
}
// 从DICOM文件中获取使用的字符集
private string GetEncodingVaulueFromDicomFile(DicomDataset dataset, DicomTag dicomTag)
{
// 获取DICOM文件的特定元素通常用于指示使用的字符集
var charset = dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
var dicomEncoding = DicomEncoding.GetEncoding(charset);
var dicomStringElement = dataset.GetDicomItem<DicomStringElement>(dicomTag);
var bytes = dicomStringElement.Buffer.Data;
return dicomEncoding.GetString(bytes);
//// 从DICOM文件中获取使用的字符集
//string filePath = "C:\\Users\\hang\\Documents\\WeChat Files\\wxid_r2imdzb7j3q922\\FileStorage\\File\\2024-05\\1.2.840.113619.2.80.169103990.5390.1271401378.4.dcm";
//DicomFile dicomFile = DicomFile.Open(filePath);
//// 获取DICOM文件的特定元素通常用于指示使用的字符集
//var charset = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SpecificCharacterSet, string.Empty);
//var dicomEncoding = DicomEncoding.GetEncoding(charset);
//var value = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
//var dicomStringElement = dicomFile.Dataset.GetDicomItem<DicomStringElement>(DicomTag.PatientName);
//var bytes = dicomStringElement.Buffer.Data;
//var aa= dicomEncoding.GetString(bytes);
}
}
}

View File

@ -1,11 +0,0 @@
using FellowOakDicom;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace IRaCIS.Core.SCP.Service
{
public interface IDicomArchiveService
{
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
}
}

View File

@ -1,770 +0,0 @@
using AlibabaCloud.SDK.Sts20150401;
using Aliyun.OSS;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
using MassTransit;
using Microsoft.Extensions.Options;
using Minio;
using Minio.DataModel.Args;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
namespace IRaCIS.Core.SCP;
#region 绑定和返回模型
[LowerCamelCaseJson]
public class MinIOOptions : AWSOptions
{
public int Port { get; set; }
}
public class AWSOptions
{
public string EndPoint { get; set; }
public bool UseSSL { get; set; }
public string AccessKeyId { get; set; }
public string RoleArn { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
public string Region { get; set; }
}
public class AliyunOSSOptions
{
public string RegionId { get; set; }
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string InternalEndpoint { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string RoleArn { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public int DurationSeconds { get; set; }
}
public class ObjectStoreServiceOptions
{
public string ObjectStoreUse { get; set; }
public AliyunOSSOptions AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSOptions AWS { get; set; }
}
public class ObjectStoreDTO
{
public string ObjectStoreUse { get; set; }
public AliyunOSSTempToken AliyunOSS { get; set; }
public MinIOOptions MinIO { get; set; }
public AWSTempToken AWS { get; set; }
}
[LowerCamelCaseJson]
public class AliyunOSSTempToken
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string EndPoint { get; set; }
public string BucketName { get; set; }
public string Region { get; set; }
public string ViewEndpoint { get; set; }
public string SecurityToken { get; set; }
public DateTime Expiration { get; set; }
}
[LowerCamelCaseJson]
public class AWSTempToken
{
public string Region { get; set; }
public string SessionToken { get; set; }
public string EndPoint { get; set; }
public string AccessKeyId { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public DateTime Expiration { get; set; }
}
public enum ObjectStoreUse
{
AliyunOSS = 0,
MinIO = 1,
AWS = 2,
}
#endregion
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
public interface IOSSService
{
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
public Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath);
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
public Task<string> GetSignedUrl(string ossRelativePath);
public Task DeleteFromPrefix(string prefix);
public ObjectStoreDTO GetObjectStoreTempToken();
}
public class OSSService : IOSSService
{
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
private AWSTempToken AWSTempToken { get; set; }
public OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options)
{
ObjectStoreServiceOptions = options.CurrentValue;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="fileStream"></param>
/// <param name="oosFolderPath"></param>
/// <param name="fileRealName"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
try
{
using (var memoryStream = new MemoryStream())
{
fileStream.Seek(0, SeekOrigin.Begin);
fileStream.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, memoryStream);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithStreamData(memoryStream)
.WithObjectSize(memoryStream.Length);
await minioClient.PutObjectAsync(putObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
InputStream = memoryStream,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException($"上传发生异常:{ex.Message}");
}
return "/" + ossRelativePath;
}
/// <summary>
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
/// </summary>
/// <param name="localFilePath"></param>
/// <param name="oosFolderPath"></param>
/// <param name="isFileNameAddGuid"></param>
/// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception>
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
{
GetObjectStoreTempToken();
var localFileName = Path.GetFileName(localFilePath);
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.PutObject(aliConfig.BucketName, ossRelativePath, localFilePath);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var putObjectArgs = new PutObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFileName(localFilePath);
await minioClient.PutObjectAsync(putObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var putObjectRequest = new Amazon.S3.Model.PutObjectRequest()
{
BucketName = awsConfig.BucketName,
FilePath = localFilePath,
Key = ossRelativePath,
};
await amazonS3Client.PutObjectAsync(putObjectRequest);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
return "/" + ossRelativePath;
}
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 上传文件
var result = _ossClient.GetObject(aliConfig.BucketName, ossRelativePath);
// 将下载的文件流保存到本地文件
using (var fs = File.OpenWrite(localFilePath))
{
result.Content.CopyTo(fs);
fs.Close();
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var getObjectArgs = new GetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithFile(localFilePath);
await minioClient.GetObjectAsync(getObjectArgs);
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var getObjectArgs = new Amazon.S3.Model.GetObjectRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
};
await (await amazonS3Client.GetObjectAsync(getObjectArgs)).WriteResponseStreamToFileAsync(localFilePath, true, CancellationToken.None);
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss下载失败!" + ex.Message);
}
}
public async Task<string> GetSignedUrl(string ossRelativePath)
{
GetObjectStoreTempToken();
ossRelativePath = ossRelativePath.TrimStart('/');
try
{
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
// 生成签名URL。
var req = new GeneratePresignedUriRequest(aliConfig.BucketName, ossRelativePath, SignHttpMethod.Get)
{
// 设置签名URL过期时间默认值为3600秒。
Expiration = DateTime.Now.AddHours(1),
};
var uri = _ossClient.GeneratePresignedUri(req);
return uri.PathAndQuery;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var args = new PresignedGetObjectArgs()
.WithBucket(minIOConfig.BucketName)
.WithObject(ossRelativePath)
.WithExpiry(3600)
/*.WithHeaders(reqParams)*/;
var presignedUrl = await minioClient.PresignedGetObjectAsync(args);
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
var presignedUrl = await amazonS3Client.GetPreSignedURLAsync(new GetPreSignedUrlRequest()
{
BucketName = awsConfig.BucketName,
Key = ossRelativePath,
Expires = DateTime.UtcNow.AddMinutes(120)
});
Uri uri = new Uri(presignedUrl);
string relativePath = uri.PathAndQuery;
return relativePath;
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
catch (Exception ex)
{
throw new BusinessValidationFailedException("oss授权url失败!" + ex.Message);
}
}
/// <summary>
/// 删除某个目录的文件
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
public async Task DeleteFromPrefix(string prefix)
{
GetObjectStoreTempToken();
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
try
{
ObjectListing objectListing = null;
string nextMarker = null;
do
{
// 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker
objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(aliConfig.BucketName)
{
Prefix = prefix,
MaxKeys = 1000,
Marker = nextMarker
});
List<string> keys = objectListing.ObjectSummaries.Select(t => t.Key).ToList();
// 删除获取到的文件
if (keys.Count > 0)
{
_ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, keys, false));
}
// 设置 NextMarker 以获取下一页的数据
nextMarker = objectListing.NextMarker;
} while (objectListing.IsTruncated);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
var minIOConfig = ObjectStoreServiceOptions.MinIO;
var minioClient = new MinioClient().WithEndpoint($"{minIOConfig.EndPoint}:{minIOConfig.Port}")
.WithCredentials(minIOConfig.AccessKeyId, minIOConfig.SecretAccessKey).WithSSL(minIOConfig.UseSSL)
.Build();
var listArgs = new ListObjectsArgs().WithBucket(minIOConfig.BucketName).WithPrefix(prefix).WithRecursive(true);
// 创建一个空列表用于存储对象键
var objects = new List<string>();
// 使用 await foreach 来异步迭代对象列表
await foreach (var item in minioClient.ListObjectsEnumAsync(listArgs))
{
objects.Add(item.Key);
}
if (objects.Count > 0)
{
var objArgs = new RemoveObjectsArgs()
.WithBucket(minIOConfig.BucketName)
.WithObjects(objects);
// 删除对象
await minioClient.RemoveObjectsAsync(objArgs);
}
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsConfig = ObjectStoreServiceOptions.AWS;
// 提供awsAccessKeyId和awsSecretAccessKey构造凭证
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
//提供awsEndPoint域名进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
// 列出指定前缀下的所有对象
var listObjectsRequest = new ListObjectsV2Request
{
BucketName = awsConfig.BucketName,
Prefix = prefix
};
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
if (listObjectsResponse.S3Objects.Count > 0)
{
// 准备删除请求
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
{
BucketName = awsConfig.BucketName,
Objects = new List<KeyVersion>()
};
foreach (var s3Object in listObjectsResponse.S3Objects)
{
deleteObjectsRequest.Objects.Add(new KeyVersion
{
Key = s3Object.Key
});
}
// 批量删除对象
var deleteObjectsResponse = await amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);
}
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
public ObjectStoreDTO GetObjectStoreTempToken()
{
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
{
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
{
AccessKeyId = ossOptions.AccessKeyId,
AccessKeySecret = ossOptions.AccessKeySecret,
//AccessKeyId = "LTAI5tJV76pYX5yPg1N9QVE8",
//AccessKeySecret = "roRNLa9YG1of4pYruJGCNKBXEWTAWa",
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
});
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称例如oss-role-session。
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
var credentials = response.Body.Credentials;
var tempToken = new AliyunOSSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
AccessKeySecret = credentials.AccessKeySecret,
//转为服务器时区,最后统一转为客户端时区
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
SecurityToken = credentials.SecurityToken,
Region = ossOptions.Region,
BucketName = ossOptions.BucketName,
EndPoint = ossOptions.EndPoint,
ViewEndpoint = ossOptions.ViewEndpoint,
};
AliyunOSSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AliyunOSS = tempToken };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "MinIO")
{
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, MinIO = ObjectStoreServiceOptions.MinIO };
}
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
{
var awsOptions = ObjectStoreServiceOptions.AWS;
//aws 临时凭证
// 创建 STS 客户端
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
// 使用 AssumeRole 请求临时凭证
var assumeRoleRequest = new AssumeRoleRequest
{
RoleArn = awsOptions.RoleArn, // 角色 ARN
RoleSessionName = $"session-name-{NewId.NextGuid()}",
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
};
var assumeRoleResponse = stsClient.AssumeRoleAsync(assumeRoleRequest).Result;
var credentials = assumeRoleResponse.Credentials;
var tempToken = new AWSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
SecretAccessKey = credentials.SecretAccessKey,
SessionToken = credentials.SessionToken,
Expiration = credentials.Expiration,
Region = awsOptions.Region,
BucketName = awsOptions.BucketName,
EndPoint = awsOptions.EndPoint,
ViewEndpoint = awsOptions.ViewEndpoint,
};
AWSTempToken = tempToken;
return new ObjectStoreDTO() { ObjectStoreUse = ObjectStoreServiceOptions.ObjectStoreUse, AWS = tempToken };
}
else
{
throw new BusinessValidationFailedException("未定义的存储介质类型");
}
}
}

View File

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

View File

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

View File

@ -77,7 +77,7 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput)) else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
{ {
//---程序错误,请联系开发人员。 //---程序错误,请联系开发人员。
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError")); var apiResponse = ResponseOutput.NotOk(StaticData.International("UnifiedAPI_ProgramError"));
objectResult.Value = apiResponse; objectResult.Value = apiResponse;
objectResult.DeclaredType = apiResponse.GetType(); objectResult.DeclaredType = apiResponse.GetType();

View File

@ -7,28 +7,28 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" /> <PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.4" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" /> <PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" /> <PackageReference Include="AWSSDK.S3" Version="3.7.402.7" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" /> <PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.16" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" /> <PackageReference Include="DistributedLock.Core" Version="1.0.7" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" /> <PackageReference Include="DistributedLock.SqlServer" Version="1.0.5" />
<PackageReference Include="fo-dicom" Version="5.2.1" /> <PackageReference Include="fo-dicom" Version="5.1.3" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" /> <PackageReference Include="fo-dicom.Codecs" Version="5.14.5" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" /> <PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
<PackageReference Include="AutoMapper" Version="13.0.1" /> <PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" /> <PackageReference Include="Minio" Version="6.0.3" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0"> <PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" /> <PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" /> <PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -126,22 +126,16 @@ builder.Services.Configure<ForwardedHeadersOptions>(options =>
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
}); });
builder.Services.AddFellowOakDicom().AddTranscoderManager<NativeTranscoderManager>()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>();
//Dicom影像渲染图片 跨平台 //Dicom影像渲染图片 跨平台
//builder.Services.AddDicomSetup(); //builder.Services.AddDicomSetup();
//new DicomSetupBuilder() new DicomSetupBuilder()
// .RegisterServices(s => .RegisterServices(s =>
// s.AddFellowOakDicom() s.AddFellowOakDicom()
// .AddTranscoderManager<NativeTranscoderManager>() .AddTranscoderManager<NativeTranscoderManager>()
// //.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>() //.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
// .AddImageManager<ImageSharpImageManager>()) .AddImageManager<ImageSharpImageManager>())
// .SkipValidation() .SkipValidation()
// .Build(); .Build();
@ -217,8 +211,6 @@ else
#endregion #endregion
DicomSetupBuilder.UseServiceProvider(app.Services);
var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>(); var logger = app.Services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();
var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services,logger: logger); var server = DicomServerFactory.Create<CStoreSCPService>(_configuration.GetSection("DicomSCPServiceConfig").GetValue<int>("ServerPort"), userState: app.Services,logger: logger);

View File

@ -20,16 +20,6 @@
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Uat_IRC_SCP" "ASPNETCORE_ENVIRONMENT": "Uat_IRC_SCP"
} }
},
"US_Prod_IRC_SCP": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:6200",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Prod_SCP"
}
} }
} }
} }

View File

@ -76,10 +76,9 @@ namespace IRaCIS.Core.SCP.Service
}; };
public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies, IServiceProvider injectServiceProvider) public CStoreSCPService(INetworkStream stream, Encoding fallbackEncoding, Microsoft.Extensions.Logging.ILogger log, DicomServiceDependencies dependencies)
: base(stream, fallbackEncoding, log, dependencies) : base(stream, fallbackEncoding, log, dependencies)
{ {
_serviceProvider = injectServiceProvider.CreateScope().ServiceProvider;
} }
@ -93,7 +92,9 @@ namespace IRaCIS.Core.SCP.Service
Log.Logger.Warning($"接收到来自{association.CallingAE}的连接"); Log.Logger.Warning($"接收到来自{association.CallingAE}的连接");
//_serviceProvider = (IServiceProvider)this.UserState; _serviceProvider = (IServiceProvider)this.UserState;
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>(); var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
@ -274,7 +275,7 @@ namespace IRaCIS.Core.SCP.Service
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>(); var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var storeRelativePath = string.Empty; var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}"; var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}{studyInstanceUid}";
long fileSize = 0; long fileSize = 0;
@ -314,7 +315,7 @@ namespace IRaCIS.Core.SCP.Service
_SCPStudyIdList.Add(scpStudyId); _SCPStudyIdList.Add(scpStudyId);
} }
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId); var series = await _seriesRepository.FindAsync(seriesId);
//没有缩略图 //没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath)) if (series != null && string.IsNullOrEmpty(series.ImageResizePath))

View File

@ -75,9 +75,9 @@ namespace IRaCIS.Core.SCP.Service
//using (@lock.Acquire()) //using (@lock.Acquire())
{ {
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId ); var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId); var findStudy = await _studyRepository.FindAsync(studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId); var findSerice = await _seriesRepository.FindAsync(seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId); 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); 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);
@ -307,7 +307,7 @@ namespace IRaCIS.Core.SCP.Service
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize }); await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
} }
await _studyRepository.SaveChangesAsync(); //await _studyRepository.SaveChangesAsync();
return findStudy.Id; return findStudy.Id;
} }

View File

@ -21,8 +21,8 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=us-mssql-prod,1433;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true", "RemoteNew": "Server=us-prod-mssql-service,1433;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=us-mssql-prod,1433;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true" "Hangfire": "Server=us-prod-mssql-service,1433;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
}, },
"DicomSCPServiceConfig": { "DicomSCPServiceConfig": {
"CalledAEList": [ "CalledAEList": [

View File

@ -12,18 +12,18 @@
"RegionId": "cn-shanghai", "RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com", "InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com", "EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y", "AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T", "AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access", "RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
"BucketName": "tl-med-irc-uat-store", "BucketName": "zy-irc-uat-store",
"ViewEndpoint": "https://tl-med-irc-uat-store.oss-cn-shanghai.aliyuncs.com", "ViewEndpoint": "https://zy-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai", "Region": "oss-cn-shanghai",
"DurationSeconds": 7200 "DurationSeconds": 7200
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true", "RemoteNew": "Server=47.117.164.182,1434;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true" "Hangfire": "Server=47.117.164.182,1434;Database=Uat_IRC.Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
}, },
"DicomSCPServiceConfig": { "DicomSCPServiceConfig": {
"CalledAEList": [ "CalledAEList": [

View File

@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRaCIS.Core.Infrastructure"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRC.Core.SCP", "IRC.Core.SCP\IRC.Core.SCP.csproj", "{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRC.Core.Dicom", "IRC.Core.Dicom\IRC.Core.Dicom.csproj", "{0545F0A5-D97B-4A47-92A6-A8A02A181322}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -59,10 +57,6 @@ Global
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU {ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.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.ActiveCfg = Release|Any CPU
{ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.Build.0 = Release|Any CPU {ECD08F47-DC1A-484E-BB91-6CDDC8823CC5}.Release|Any CPU.Build.0 = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0545F0A5-D97B-4A47-92A6-A8A02A181322}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -25,12 +25,12 @@ namespace EasyCaching.Demo.Interceptors.Controllers
{ {
ControllerContext.HttpContext.Response.StatusCode = 401; ControllerContext.HttpContext.Response.StatusCode = 401;
} }
return ResponseOutput.NotOk($"Client error, actual request error status code({code})"); return ResponseOutput.NotOk($"Client error, actual request error status code({code})");
} }
else else
{ {
return ResponseOutput.NotOk($"Server error , actual request error status code({code})"); return ResponseOutput.NotOk($"Server error , actual request error status code({code})");
} }

View File

@ -1,42 +1,43 @@
using AlibabaCloud.SDK.Sts20150401; using System;
using Amazon.Auth.AccessControlPolicy;
using Amazon.SecurityToken;
using AutoMapper;
using Azure.Core;
using IdentityModel.Client;
using IdentityModel.OidcClient;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.Auth;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using MassTransit;
using MassTransit.Futures.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Org.BouncyCastle.Tls;
using RestSharp;
using RestSharp.Authenticators;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Auth;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.Json; using Microsoft.AspNetCore.Http;
using IRaCIS.Core.Application.Interfaces;
using System.Threading.Tasks; using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion; using IRaCIS.Core.Application.Service;
using AssumeRoleRequest = Amazon.SecurityToken.Model.AssumeRoleRequest; using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infrastructure;
using System.Linq;
using Microsoft.Extensions.Logging;
using MassTransit;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Application.Helper;
using Microsoft.Extensions.Options;
using IRaCIS.Core.Application.Contracts;
using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO; using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO;
using DocumentFormat.OpenXml.Spreadsheet;
using AutoMapper.QueryableExtensions;
using NetTopologySuite.Algorithm;
using ZiggyCreatures.Caching.Fusion;
using AlibabaCloud.SDK.Sts20150401;
using AlibabaCloud.SDK.Sts20150401.Models;
using Org.BouncyCastle.Tls;
using Amazon.SecurityToken.Model;
using Amazon.SecurityToken;
using Amazon;
using AssumeRoleRequest = Amazon.SecurityToken.Model.AssumeRoleRequest;
using AutoMapper;
namespace IRaCIS.Api.Controllers namespace IRaCIS.Api.Controllers
{ {
@ -44,10 +45,7 @@ namespace IRaCIS.Api.Controllers
/// 医生基本信息 、工作信息 专业信息、审核状态 /// 医生基本信息 、工作信息 专业信息、审核状态
/// </summary> /// </summary>
[ApiController, ApiExplorerSettings(GroupName = "Reviewer")] [ApiController, ApiExplorerSettings(GroupName = "Reviewer")]
public class ExtraController([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService, public class ExtraController : ControllerBase
[FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService,
[FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService) : ControllerBase
{ {
@ -62,40 +60,34 @@ namespace IRaCIS.Api.Controllers
/// <param name="_vacationService"></param> /// <param name="_vacationService"></param>
/// <param name="doctorId"></param> /// <param name="doctorId"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("doctor/getDetail")] [HttpGet, Route("doctor/getDetail/{doctorId:guid}")]
public async Task<IResponseOutput<DoctorDetailDTO>> GetDoctorDetail(GetDoctorDetailInDto inDto) public async Task<IResponseOutput<DoctorDetailDTO>> GetDoctorDetail([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService,
[FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService,
[FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService, Guid doctorId)
{ {
var education = await _educationService.GetEducation(inDto.doctorId); var education = await _educationService.GetEducation(doctorId);
var sowList = _doctorService.GetDoctorSowList(inDto.doctorId); var sowList = _doctorService.GetDoctorSowList(doctorId);
var ackSowList = _doctorService.GetDoctorAckSowList(inDto.doctorId); var ackSowList = _doctorService.GetDoctorAckSowList(doctorId);
var doctorDetail = new DoctorDetailDTO var doctorDetail = new DoctorDetailDTO
{ {
AuditView = await _doctorService.GetAuditState(inDto.doctorId), AuditView = await _doctorService.GetAuditState(doctorId),
BasicInfoView = await _doctorService.GetBasicInfo(inDto.doctorId), BasicInfoView = await _doctorService.GetBasicInfo(doctorId),
EmploymentView = await _doctorService.GetEmploymentInfo(inDto.doctorId), EmploymentView = await _doctorService.GetEmploymentInfo(doctorId),
AttachmentList = await attachmentService.GetAttachments(inDto.doctorId), AttachmentList = await attachmentService.GetAttachments(doctorId),
SummarizeInfo = await _doctorService.GetSummarizeInfo(new GetSummarizeInfoInDto()
{
DoctorId = inDto.doctorId,
TrialId = inDto.TrialId
}),
PaymentModeInfo = await _doctorService.GetPaymentMode(inDto.doctorId),
EducationList = education.EducationList, EducationList = education.EducationList,
PostgraduateList = education.PostgraduateList, PostgraduateList = education.PostgraduateList,
TrialExperienceView = await _trialExperienceService.GetTrialExperience(new TrialExperienceModelIndto() TrialExperienceView = await _trialExperienceService.GetTrialExperience(doctorId),
{ ResearchPublicationView = await _researchPublicationService.GetResearchPublication(doctorId),
DoctorId = inDto.doctorId,
TrialId = inDto.TrialId
}),
ResearchPublicationView = await _researchPublicationService.GetResearchPublication(inDto.doctorId),
SpecialtyView = await _doctorService.GetSpecialtyInfo(inDto.doctorId), SpecialtyView = await _doctorService.GetSpecialtyInfo(doctorId),
InHoliday = (await _vacationService.OnVacation(inDto.doctorId)).IsSuccess, InHoliday = (await _vacationService.OnVacation(doctorId)).IsSuccess,
IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(inDto.doctorId), IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(doctorId),
SowList = sowList, SowList = sowList,
AckSowList = ackSowList AckSowList = ackSowList
}; };
@ -109,6 +101,196 @@ namespace IRaCIS.Api.Controllers
/// <summary> 系统用户登录接口[New] </summary>
[HttpPost, Route("user/login")]
[AllowAnonymous]
public async Task<IResponseOutput> Login(UserLoginDTO loginUser,
[FromServices] IFusionCache _fusionCache,
[FromServices] IUserService _userService,
[FromServices] ITokenService _tokenService,
[FromServices] IReadingImageTaskService readingImageTaskService,
[FromServices] IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig,
[FromServices] IOptionsMonitor<SystemEmailSendConfig> _emailConfig,
[FromServices] IMailVerificationService _mailVerificationService)
{
var emailConfig = _emailConfig.CurrentValue;
var companyInfo = new SystemEmailSendConfigView() { CompanyName = emailConfig.CompanyName, CompanyNameCN = emailConfig.CompanyNameCN, CompanyShortName = emailConfig.CompanyShortName, CompanyShortNameCN = emailConfig.CompanyShortNameCN };
//MFA 邮箱验证 前端传递用户Id 和MFACode
if (loginUser.UserId != null && _verifyConfig.CurrentValue.OpenLoginMFA)
{
Guid userId = (Guid)loginUser.UserId;
//验证MFA 编码是否有问题 ,前端要拆开,自己调用验证的逻辑
//await _userService.VerifyMFACodeAsync(userId, loginUser.MFACode);
//var loginUser = await _userRepository.Where(u => u.UserName.Equals(userName) && u.Password == password).ProjectTo<UserBasicInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync();
var basicInfo = await _userService.GetUserBasicInfo(userId, loginUser.Password);
var loginReturn = new LoginReturnDTO() { BasicInfo = basicInfo };
loginReturn.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(loginReturn.BasicInfo));
// 创建一个 CookieOptions 对象,用于设置 Cookie 的属性
var option = new CookieOptions
{
Expires = DateTime.Now.AddMonths(1), // 设置过期时间为 30 分钟之后
HttpOnly = false, // 确保 cookie 只能通过 HTTP 访问
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None, // 设置 SameSite 属性
Secure = false // 确保 cookie 只能通过 HTTPS 访问
};
HttpContext.Response.Cookies.Append("access_token", loginReturn.JWTStr, option);
// 验证阅片休息时间
await readingImageTaskService.ResetReadingRestTime(userId);
await _fusionCache.SetAsync(CacheKeys.UserToken(userId), loginReturn.JWTStr, TimeSpan.FromDays(7));
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(userId), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(_verifyConfig.CurrentValue.AutoLoginOutMinutes));
loginReturn.CompanyInfo = companyInfo;
return ResponseOutput.Ok(loginReturn);
}
else
{
var returnModel = await _userService.Login(loginUser.UserName, loginUser.Password);
if (returnModel.IsSuccess)
{
#region GRPC 调用鉴权中心因为服务器IIS问题 http/2 故而没法使用
////重试策略
//var defaultMethodConfig = new MethodConfig
//{
// Names = { MethodName.Default },
// RetryPolicy = new RetryPolicy
// {
// MaxAttempts = 3,
// InitialBackoff = TimeSpan.FromSeconds(1),
// MaxBackoff = TimeSpan.FromSeconds(5),
// BackoffMultiplier = 1.5,
// RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable }
// }
//};
//#region unable to trust the certificate then the gRPC client can be configured to ignore the invalid certificate
//var httpHandler = new HttpClientHandler();
//// Return `true` to allow certificates that are untrusted/invalid
//httpHandler.ServerCertificateCustomValidationCallback =
// HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
//////这一句是让grpc支持本地 http 如果本地访问部署在服务器上,那么是访问不成功的
//AppContext.SetSwitch(
// "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
//#endregion
//var grpcAdress = configuration.GetValue<string>("GrpcAddress");
////var grpcAdress = "http://localhost:7200";
//var channel = GrpcChannel.ForAddress(grpcAdress, new GrpcChannelOptions
//{
// HttpHandler = httpHandler,
// ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
//});
////var channel = GrpcChannel.ForAddress(grpcAdress);
//var grpcClient = new TokenGrpcService.TokenGrpcServiceClient(channel);
//var userInfo = returnModel.Data.BasicInfo;
//var tokenResponse = grpcClient.GetUserToken(new GetTokenReuqest()
//{
// Id = userInfo.Id.ToString(),
// ReviewerCode = userInfo.ReviewerCode,
// IsAdmin = userInfo.IsAdmin,
// RealName = userInfo.RealName,
// UserTypeEnumInt = (int)userInfo.UserTypeEnum,
// UserTypeShortName = userInfo.UserTypeShortName,
// UserName = userInfo.UserName
//});
//returnModel.Data.JWTStr = tokenResponse.Token;
#endregion
var userId = returnModel.Data.BasicInfo.Id;
if (_verifyConfig.CurrentValue.OpenLoginMFA)
{
//MFA 发送邮件
returnModel.Data.IsMFA = true;
var email = returnModel.Data.BasicInfo.EMail;
var hiddenEmail = IRCEmailPasswordHelper.MaskEmail(email);
returnModel.Data.BasicInfo.EMail = hiddenEmail;
//修改密码
if (returnModel.Data.BasicInfo.IsFirstAdd || returnModel.Data.BasicInfo.LoginState == 1)
{
returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo));
}
else
{
//正常登录才发送邮件
await _userService.SendMFAEmail(userId);
}
}
else
{
returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo));
// 创建一个 CookieOptions 对象,用于设置 Cookie 的属性
var option = new CookieOptions
{
Expires = DateTime.Now.AddMonths(1), // 设置过期时间为 30 分钟之后
HttpOnly = false, // 确保 cookie 只能通过 HTTP 访问
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None, // 设置 SameSite 属性
Secure = false // 确保 cookie 只能通过 HTTPS 访问
};
HttpContext.Response.Cookies.Append("access_token", returnModel.Data.JWTStr, option);
// 验证阅片休息时间
await readingImageTaskService.ResetReadingRestTime(returnModel.Data.BasicInfo.Id);
await _fusionCache.SetAsync(CacheKeys.UserToken(userId), returnModel.Data.JWTStr, TimeSpan.FromDays(7));
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(userId), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(_verifyConfig.CurrentValue.AutoLoginOutMinutes));
}
}
returnModel.Data.CompanyInfo = companyInfo;
return returnModel;
}
}
[AllowAnonymous] [AllowAnonymous]
[HttpGet, Route("user/getPublicKey")] [HttpGet, Route("user/getPublicKey")]
public IResponseOutput GetPublicKey([FromServices] IOptionsMonitor<IRCEncreptOption> _IRCEncreptOption) public IResponseOutput GetPublicKey([FromServices] IOptionsMonitor<IRCEncreptOption> _IRCEncreptOption)
@ -123,13 +305,18 @@ namespace IRaCIS.Api.Controllers
[AllowAnonymous] [AllowAnonymous]
public IResponseOutput ShareImage([FromServices] ITokenService _tokenService) public IResponseOutput ShareImage([FromServices] ITokenService _tokenService)
{ {
var token = _tokenService.GetToken(new UserTokenInfo() var token = _tokenService.GetToken(IRaCISClaims.Create(new UserBasicInfo()
{ {
IdentityUserId = Guid.NewGuid(), Id = Guid.Empty,
IsReviewer = false,
IsAdmin = false,
RealName = "Share001",
UserName = "Share001", UserName = "Share001",
Sex = 0,
//UserType = "ShareType",
UserTypeEnum = UserTypeEnum.ShareImage, UserTypeEnum = UserTypeEnum.ShareImage,
Code = "ShareCode001",
}); }));
return ResponseOutput.Ok("/showdicom?studyId=f7b67793-8155-0223-2f15-118f2642efb8&type=Share&token=" + token); return ResponseOutput.Ok("/showdicom?studyId=f7b67793-8155-0223-2f15-118f2642efb8&type=Share&token=" + token);
} }
@ -184,74 +371,54 @@ namespace IRaCIS.Api.Controllers
} }
#region 老项目依赖 #region aliyun-net-sdk-sts 之前
//[HttpGet("user/GenerateSTS")]
[HttpGet("user/GenerateSTS")] //public IResponseOutput GenerateSTS([FromServices] IOptionsMonitor<AliyunOSSOptions> options)
public IResponseOutput GenerateSTS([FromServices] IOptionsMonitor<ObjectStoreServiceOptions> options) //{
{ // var ossOptions = options.CurrentValue;
var ossOptions = options.CurrentValue.AliyunOSS;
var client = new Client(new AlibabaCloud.OpenApiClient.Models.Config()
{
AccessKeyId = ossOptions.AccessKeyId,
AccessKeySecret = ossOptions.AccessKeySecret,
Endpoint = "sts.cn-hangzhou.aliyuncs.com"
});
var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
// 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称例如oss-role-session。
assumeRoleRequest.RoleSessionName = $"session-name-{NewId.NextGuid()}";
// 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
assumeRoleRequest.RoleArn = ossOptions.RoleArn;
//assumeRoleRequest.RoleArn = "acs:ram::1899121822495495:role/webdirect";
assumeRoleRequest.DurationSeconds = ossOptions.DurationSeconds;
var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
var credentials = response.Body.Credentials;
var tempToken = new AliyunOSSTempToken()
{
AccessKeyId = credentials.AccessKeyId,
AccessKeySecret = credentials.AccessKeySecret,
//转为服务器时区,最后统一转为客户端时区
Expiration = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(credentials.Expiration), TimeZoneInfo.Local),
SecurityToken = credentials.SecurityToken,
Region = ossOptions.Region, // IClientProfile profile = DefaultProfile.GetProfile(ossOptions.regionId, ossOptions.accessKeyId, ossOptions.accessKeySecret);
BucketName = ossOptions.BucketName, // DefaultAcsClient client = new DefaultAcsClient(profile);
EndPoint = ossOptions.EndPoint,
ViewEndpoint = ossOptions.ViewEndpoint,
PreviewEndpoint = ossOptions.PreviewEndpoint
};
// 返回STS令牌信息给前端 // // 创建一个STS请求
var stsToken = new // AssumeRoleRequest request = new AssumeRoleRequest
{ // {
AccessKeyId = credentials.AccessKeyId, // RoleArn = ossOptions.roleArn, // 角色ARN需要替换为你的角色ARN
AccessKeySecret = credentials.AccessKeySecret, // RoleSessionName = $"session-name-{NewId.NextGuid()}", // 角色会话名称,可自定义
SecurityToken = credentials.SecurityToken, // DurationSeconds = 900, // 令牌有效期单位这里设置为1小时
Expiration = credentials.Expiration, // };
Region = ossOptions.Region,
BucketName = ossOptions.BucketName,
ViewEndpoint = ossOptions.ViewEndpoint,
}; // AssumeRoleResponse response = client.GetAcsResponse(request);
return ResponseOutput.Ok(stsToken); // // 返回STS令牌信息给前端
// var stsToken = new
// {
// AccessKeyId = response.Credentials.AccessKeyId,
// AccessKeySecret = response.Credentials.AccessKeySecret,
// SecurityToken = response.Credentials.SecurityToken,
// Expiration = response.Credentials.Expiration,
} // Region = ossOptions.region,
// BucketName = ossOptions.bucketName,
// ViewEndpoint = ossOptions.viewEndpoint,
// };
// return ResponseOutput.Ok(stsToken);
//}
#endregion #endregion
[HttpGet("User/UserRedirect")] [HttpGet("User/UserRedirect")]
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> UserRedirect([FromServices] IRepository<IdentityUser> _useRepository, string url, [FromServices] ILogger<ExtraController> _logger, [FromServices] ITokenService _tokenService) public async Task<IActionResult> UserRedirect([FromServices] IRepository<User> _userRepository, string url, [FromServices] ILogger<ExtraController> _logger)
{ {
var decodeUrl = System.Web.HttpUtility.UrlDecode(url); var decodeUrl = System.Web.HttpUtility.UrlDecode(url);
@ -266,22 +433,10 @@ namespace IRaCIS.Api.Controllers
var errorUrl = domainStrList[0] + "//" + domainStrList[2] + "/error"; var errorUrl = domainStrList[0] + "//" + domainStrList[2] + "/error";
if (lang == "zh")
{
CultureInfo.CurrentCulture = new CultureInfo(StaticData.CultureInfo.zh_CN);
CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.zh_CN);
}
else
{
CultureInfo.CurrentCulture = new CultureInfo(StaticData.CultureInfo.en_US);
CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.en_US);
}
var isExpire = _tokenService.IsTokenExpired(token); if (!await _userRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd))
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire)
{ {
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} "; decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(lang == "zh" ? "" : "ErrorThe initialization link has expired. Return")} ";
} }
return Redirect(decodeUrl); return Redirect(decodeUrl);
@ -290,47 +445,7 @@ namespace IRaCIS.Api.Controllers
#region 项目支持Oauth 对接修改
/// <summary>
/// 回调到前端,前端调用后端的接口
/// 参考链接https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
/// 后端通过这个code ,带上客户端信息,和授权类型 可以向单点登录提供商获取厂商token
///
/// 但是单点登录提供商提供的token 和我们系统的token 是有区别的我们的token里面有我们业务系统的UserId涉及到很多业务操作所以在此出现了两种方案
/// 1、前端使用厂商的Token。 后端通过code 获取厂商的Token 返回前端的同时返回我们系统的UserId前段在http 请求头加上一个自定义参数带上UserId 后端取用户Id的地方变动下
/// 但是除了UserId外后端还有其他信息也是从Token取的所以在请求头也需要带上此外后端认证Token的方式也需要变化改造成本稍大如果是微服务做这种处理还是可以的
/// 2、前端还是使用我们后台自己的Token。后端通过code 获取厂商Token的同时后端做一个隐藏登录返回厂商的Token的同时也返回我们系统的Token。
/// (像我们单体,这种方式最简单,我们用单点登录,无非就是不想记多个系统的密码,自动登录而已,其他不支持的项目改造成本也是最低的)
/// </summary>
/// <param name="type">回调的厂商类型 比如github, google, 我们用的logto ,不同的厂商回调到前端的地址可以不同的,但是请求后端的接口可以是同一个 </param>
/// <param name="code">在第三方平台登录成功后回调前端的时候会返回一个code </param>
/// <returns></returns>
[HttpGet("User/OAuthCallBack")]
public async Task<IResponseOutput> OAuthCallBack(string type, string code)
{
#region 获取AccessTo
//var headerDic = new Dictionary<string, string>();
//headerDic.Add("code", code);
//headerDic.Add("grant_type", "authorization_code");
//headerDic.Add("redirect_uri", "http://localhost:6100");
//headerDic.Add("scope", "all");
#endregion
return ResponseOutput.Ok();
}
#endregion
#region 测试获取用户 ip
[HttpGet, Route("ip")] [HttpGet, Route("ip")]
[AllowAnonymous] [AllowAnonymous]
public IResponseOutput Get([FromServices] IHttpContextAccessor _context) public IResponseOutput Get([FromServices] IHttpContextAccessor _context)
@ -371,9 +486,6 @@ namespace IRaCIS.Api.Controllers
} }
return ResponseOutput.Ok(sb.ToString()); return ResponseOutput.Ok(sb.ToString());
} }
#endregion
} }
} }

View File

@ -1,18 +1,18 @@
using IRaCIS.Application.Contracts; using Microsoft.AspNetCore.Mvc;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.Service.Inspection.DTO;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks; using System.Threading.Tasks;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.Service.Inspection.DTO;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Application.Auth;
using Microsoft.Extensions.Localization;
namespace IRaCIS.Core.API.Controllers.Special namespace IRaCIS.Core.API.Controllers.Special
{ {
@ -24,7 +24,7 @@ namespace IRaCIS.Core.API.Controllers.Special
ICalculateService _calculateService, ICalculateService _calculateService,
IStringLocalizer _localizer) : ControllerBase IStringLocalizer _localizer) : ControllerBase
{ {
//[TrialAudit(AuditType.TrialAudit, AuditOptType.AddOrUpdateTrial)] //[TrialAudit(AuditType.TrialAudit, AuditOptType.AddOrUpdateTrial)]
@ -36,8 +36,8 @@ namespace IRaCIS.Core.API.Controllers.Special
public async Task<IResponseOutput> AddOrUpdateTrialInspection(DataInspectionDto<TrialCommand> opt) public async Task<IResponseOutput> AddOrUpdateTrialInspection(DataInspectionDto<TrialCommand> opt)
{ {
var fun = await AddOrUpdateTrial(opt.Data); var fun =await AddOrUpdateTrial(opt.Data);
return fun; return fun;
} }
@ -48,33 +48,33 @@ namespace IRaCIS.Core.API.Controllers.Special
[HttpPost, Route("trial/addOrUpdateTrial")] [HttpPost, Route("trial/addOrUpdateTrial")]
//[Authorize(Policy = IRaCISPolicy.PM_APM)] //[Authorize(Policy = IRaCISPolicy.PM_APM)]
[TrialGlobalLimit( "AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })]
public async Task<IResponseOutput<Trial>> AddOrUpdateTrial(TrialCommand param) public async Task<IResponseOutput<Trial>> AddOrUpdateTrial(TrialCommand param)
{ {
//var userId = Guid.Parse(User.FindFirst("id").Value); var userId = Guid.Parse(User.FindFirst("id").Value);
var result = await _trialService.AddOrUpdateTrial(param); var result = await _trialService.AddOrUpdateTrial(param);
//if (_trialService.TrialExpeditedChange) if (_trialService.TrialExpeditedChange)
//{ {
// var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(param.Id.Value); var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(param.Id.Value);
// var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty); var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
// calcList.ForEach(t => calcList.ForEach(t =>
// { {
// if (needCalReviewerIds.Contains(t.DoctorId)) if (needCalReviewerIds.Contains(t.DoctorId))
// { {
// _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO() _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
// { {
// NeedCalculateReviewers = new List<Guid>() NeedCalculateReviewers = new List<Guid>()
// { {
// t.DoctorId t.DoctorId
// }, },
// CalculateMonth = DateTime.Parse(t.YearMonth) CalculateMonth = DateTime.Parse(t.YearMonth)
// }, User.FindFirst("id").Value); }, User.FindFirst("id").Value);
// } }
// }); });
//} }
return result; return result;
} }
@ -86,9 +86,9 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <param name="_trialWorkloadService"></param> /// <param name="_trialWorkloadService"></param>
/// <param name="workLoadAddOrUpdateModel"></param> /// <param name="workLoadAddOrUpdateModel"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("doctorWorkload/workLoadAddOrUpdate")] [HttpPost, Route("doctorWorkload/workLoadAddOrUpdate")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> WorkLoadAddOrUpdate([FromServices] IDoctorWorkloadService _trialWorkloadService, WorkloadCommand workLoadAddOrUpdateModel) public async Task<IResponseOutput> WorkLoadAddOrUpdate([FromServices] IDoctorWorkloadService _trialWorkloadService, WorkloadCommand workLoadAddOrUpdateModel)
{ {
var userId = Guid.Parse(User.FindFirst("id").Value); var userId = Guid.Parse(User.FindFirst("id").Value);
@ -108,9 +108,9 @@ namespace IRaCIS.Core.API.Controllers.Special
} }
[HttpDelete, Route("doctorWorkload/deleteWorkLoad/{id:guid}/{trialId:guid}")] [HttpDelete, Route("doctorWorkload/deleteWorkLoad/{id:guid}/{trialId:guid}")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> DeleteWorkLoad([FromServices] IDoctorWorkloadService _trialWorkloadService, Guid id) public async Task<IResponseOutput> DeleteWorkLoad([FromServices] IDoctorWorkloadService _trialWorkloadService, Guid id)
{ {
//先判断该工作量的费用是否被锁定,如果被锁定,则不能删除 //先判断该工作量的费用是否被锁定,如果被锁定,则不能删除
@ -143,7 +143,7 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <summary> /// <summary>
/// 添加或更新汇率(会触发没有对锁定的费用计算) /// 添加或更新汇率(会触发没有对锁定的费用计算)
/// </summary> /// </summary>
[HttpPost, Route("exchangeRate/addOrUpdateExchangeRate")] [HttpPost, Route("exchangeRate/addOrUpdateExchangeRate")]
public async Task<IResponseOutput> AddOrUpdateExchangeRate([FromServices] IExchangeRateService _exchangeRateService, [FromServices] IPaymentAdjustmentService _costAdjustmentService, ExchangeRateCommand addOrUpdateModel) public async Task<IResponseOutput> AddOrUpdateExchangeRate([FromServices] IExchangeRateService _exchangeRateService, [FromServices] IPaymentAdjustmentService _costAdjustmentService, ExchangeRateCommand addOrUpdateModel)
{ {
@ -153,7 +153,7 @@ namespace IRaCIS.Core.API.Controllers.Special
{ {
if (item != null) if (item != null)
{ {
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO() await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
{ {
NeedCalculateReviewers = new List<Guid>() NeedCalculateReviewers = new List<Guid>()
{ {
@ -163,7 +163,7 @@ namespace IRaCIS.Core.API.Controllers.Special
}, User.FindFirst("id").Value); }, User.FindFirst("id").Value);
} }
} }
await _costAdjustmentService.CalculateCNY(addOrUpdateModel.YearMonth, addOrUpdateModel.Rate); await _costAdjustmentService.CalculateCNY(addOrUpdateModel.YearMonth, addOrUpdateModel.Rate);
return result; return result;
} }
@ -171,13 +171,13 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <summary> /// <summary>
/// 添加或更新 职称单价[AUTH] /// 添加或更新 职称单价[AUTH]
/// </summary> /// </summary>
[HttpPost, Route("rankPrice/addOrUpdateRankPrice")] [HttpPost, Route("rankPrice/addOrUpdateRankPrice")]
public async Task<IResponseOutput> AddOrUpdateRankPrice([FromServices] IReviewerPayInfoService _reviewerPayInfoService, [FromServices] IRankPriceService _rankPriceService, RankPriceCommand addOrUpdateModel) public async Task<IResponseOutput> AddOrUpdateRankPrice([FromServices] IReviewerPayInfoService _reviewerPayInfoService, [FromServices] IRankPriceService _rankPriceService, RankPriceCommand addOrUpdateModel)
{ {
if (addOrUpdateModel.Id != Guid.Empty && addOrUpdateModel.Id != null) if (addOrUpdateModel.Id != Guid.Empty && addOrUpdateModel.Id != null)
{ {
var needCalReviewerIds = await _reviewerPayInfoService.GetReviewerIdByRankId(Guid.Parse(addOrUpdateModel.Id.ToString())); var needCalReviewerIds =await _reviewerPayInfoService.GetReviewerIdByRankId(Guid.Parse(addOrUpdateModel.Id.ToString()));
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty); var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
foreach (var item in calcList) foreach (var item in calcList)
@ -204,12 +204,12 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <summary> /// <summary>
/// 添加或更新(替换)医生支付展信息[AUTH] /// 添加或更新(替换)医生支付展信息[AUTH]
/// </summary> /// </summary>
[HttpPost, Route("reviewerPayInfo/addOrUpdateReviewerPayInfo")] [HttpPost, Route("reviewerPayInfo/addOrUpdateReviewerPayInfo")]
public async Task<IResponseOutput> AddOrUpdateReviewerPayInfo([FromServices] IReviewerPayInfoService _doctorPayInfoService, ReviewerPayInfoCommand addOrUpdateModel) public async Task<IResponseOutput> AddOrUpdateReviewerPayInfo([FromServices] IReviewerPayInfoService _doctorPayInfoService, ReviewerPayInfoCommand addOrUpdateModel)
{ {
var userId = Guid.Parse(User.FindFirst("id").Value); var userId = Guid.Parse(User.FindFirst("id").Value);
var result = await _doctorPayInfoService.AddOrUpdateReviewerPayInfo(addOrUpdateModel, userId); var result =await _doctorPayInfoService.AddOrUpdateReviewerPayInfo(addOrUpdateModel, userId);
var calcList = await _calculateService.GetNeedCalculateReviewerList(addOrUpdateModel.DoctorId, string.Empty); var calcList = await _calculateService.GetNeedCalculateReviewerList(addOrUpdateModel.DoctorId, string.Empty);
foreach (var item in calcList) foreach (var item in calcList)
{ {
@ -233,12 +233,12 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <summary> /// <summary>
/// 保存(替换)项目支付价格信息(会触发没有被锁定的费用计算)[AUTH] /// 保存(替换)项目支付价格信息(会触发没有被锁定的费用计算)[AUTH]
/// </summary> /// </summary>
[HttpPost, Route("trialPaymentPrice/addOrUpdateTrialPaymentPrice")] [HttpPost, Route("trialPaymentPrice/addOrUpdateTrialPaymentPrice")]
public async Task<IResponseOutput> AddOrUpdateTrialPaymentPrice([FromServices] ITrialPaymentPriceService _trialPaymentPriceService, TrialPaymentPriceCommand addOrUpdateModel) public async Task<IResponseOutput> AddOrUpdateTrialPaymentPrice([FromServices] ITrialPaymentPriceService _trialPaymentPriceService, TrialPaymentPriceCommand addOrUpdateModel)
{ {
var userId = Guid.Parse(User.FindFirst("id").Value); var userId = Guid.Parse(User.FindFirst("id").Value);
var result = await _trialPaymentPriceService.AddOrUpdateTrialPaymentPrice(addOrUpdateModel); var result =await _trialPaymentPriceService.AddOrUpdateTrialPaymentPrice(addOrUpdateModel);
var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(addOrUpdateModel.TrialId); var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(addOrUpdateModel.TrialId);
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty); var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
@ -246,7 +246,7 @@ namespace IRaCIS.Core.API.Controllers.Special
{ {
if (item != null && needCalReviewerIds.Contains(item.DoctorId)) if (item != null && needCalReviewerIds.Contains(item.DoctorId))
{ {
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO() await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
{ {
NeedCalculateReviewers = new List<Guid>() NeedCalculateReviewers = new List<Guid>()
{ {
@ -262,12 +262,12 @@ namespace IRaCIS.Core.API.Controllers.Special
/// <summary> /// <summary>
/// 批量更新奖励费用[AUTH] /// 批量更新奖励费用[AUTH]
/// </summary> /// </summary>
[HttpPost, Route("volumeReward/addOrUpdatevolumeRewardPriceList")] [HttpPost, Route("volumeReward/addOrUpdatevolumeRewardPriceList")]
public async Task<IResponseOutput> AddOrUpdateAwardPriceList([FromServices] IVolumeRewardService _volumeRewardService, IEnumerable<AwardPriceCommand> addOrUpdateModel) public async Task<IResponseOutput> AddOrUpdateAwardPriceList([FromServices] IVolumeRewardService _volumeRewardService, IEnumerable<AwardPriceCommand> addOrUpdateModel)
{ {
var result = await _volumeRewardService.AddOrUpdateVolumeRewardPriceList(addOrUpdateModel); var result =await _volumeRewardService.AddOrUpdateVolumeRewardPriceList(addOrUpdateModel);
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty); var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
foreach (var item in calcList) foreach (var item in calcList)

View File

@ -1,19 +1,24 @@
 
using System.Threading.Tasks;
using AutoMapper;
using IRaCIS.Application.Interfaces; using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Image.QA; using IRaCIS.Core.Application.Image.QA;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.Service; using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.Service.Inspection.DTO; using IRaCIS.Core.Application.Service.Inspection.DTO;
using IRaCIS.Core.Application.Service.Inspection.Interface; using IRaCIS.Core.Application.Service.Inspection.Interface;
using IRaCIS.Core.Application.Service.Reading.Dto; using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Application.Service.Reading.Interface; using IRaCIS.Core.Application.Service.Reading.Interface;
using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention; using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace IRaCIS.Core.API.Controllers namespace IRaCIS.Core.API.Controllers
@ -33,7 +38,7 @@ namespace IRaCIS.Core.API.Controllers
IReadingMedicineQuestionService _readingMedicineQuestionService IReadingMedicineQuestionService _readingMedicineQuestionService
) : ControllerBase ) : ControllerBase
{ {
#region 获取稽查数据 #region 获取稽查数据
/// <summary> /// <summary>
/// 获取稽查数据 /// 获取稽查数据
@ -52,7 +57,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")] [HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SetOncologyReadingInfo(DataInspectionDto<SubmitOncologyReadingInfoInDto> opt) public async Task<IResponseOutput> SetOncologyReadingInfo(DataInspectionDto<SubmitOncologyReadingInfoInDto> opt)
@ -69,7 +74,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")] [HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SubmitDicomVisitTask(DataInspectionDto<SubmitDicomVisitTaskInDto> opt) public async Task<IResponseOutput> SubmitDicomVisitTask(DataInspectionDto<SubmitDicomVisitTaskInDto> opt)
@ -88,7 +93,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")] [HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SubmitGlobalReadingInfo(DataInspectionDto<SubmitGlobalReadingInfoInDto> opt) public async Task<IResponseOutput> SubmitGlobalReadingInfo(DataInspectionDto<SubmitGlobalReadingInfoInDto> opt)
@ -107,12 +112,12 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")] [HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> TrialReadingInfoSign(DataInspectionDto<TrialReadingInfoSignInDto> opt) public async Task<IResponseOutput> TrialReadingInfoSign(DataInspectionDto<TrialReadingInfoSignInDto> opt)
{ {
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _trialConfigService.TrialReadingInfoSign(opt.Data); var result = await _trialConfigService.TrialReadingInfoSign(opt.Data);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
@ -126,7 +131,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")] [HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt) public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
@ -143,7 +148,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")] [HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> ConfirmReadingMedicineQuestion(DataInspectionDto<ConfirmReadingMedicineQuestionInDto> opt) public async Task<IResponseOutput> ConfirmReadingMedicineQuestion(DataInspectionDto<ConfirmReadingMedicineQuestionInDto> opt)
@ -161,7 +166,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")] [HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SubmitVisitTaskQuestions(DataInspectionDto<SubmitVisitTaskQuestionsInDto> opt) public async Task<IResponseOutput> SubmitVisitTaskQuestions(DataInspectionDto<SubmitVisitTaskQuestionsInDto> opt)
@ -179,7 +184,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCSignClinicalData")] [HttpPost, Route("Inspection/ClinicalAnswer/CRCSignClinicalData")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> CRCSignClinicalData(DataInspectionDto<CRCSignClinicalDataInDto> opt) public async Task<IResponseOutput> CRCSignClinicalData(DataInspectionDto<CRCSignClinicalDataInDto> opt)
@ -197,7 +202,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCConfirmClinical")] [HttpPost, Route("Inspection/ClinicalAnswer/CRCConfirmClinical")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> CRCConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt) public async Task<IResponseOutput> CRCConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
@ -214,7 +219,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/CRCCancelConfirmClinical")] [HttpPost, Route("Inspection/ClinicalAnswer/CRCCancelConfirmClinical")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> CRCCancelConfirmClinical(DataInspectionDto<CRCCancelConfirmClinicalInDto> opt) public async Task<IResponseOutput> CRCCancelConfirmClinical(DataInspectionDto<CRCCancelConfirmClinicalInDto> opt)
@ -232,7 +237,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/PMConfirmClinical")] [HttpPost, Route("Inspection/ClinicalAnswer/PMConfirmClinical")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> PMConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt) public async Task<IResponseOutput> PMConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
@ -250,7 +255,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingClinicalData/SignConsistencyAnalysisReadingClinicalData")] [HttpPost, Route("Inspection/ReadingClinicalData/SignConsistencyAnalysisReadingClinicalData")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SignConsistencyAnalysisReadingClinicalData(DataInspectionDto<SignConsistencyAnalysisReadingClinicalDataInDto> opt) public async Task<IResponseOutput> SignConsistencyAnalysisReadingClinicalData(DataInspectionDto<SignConsistencyAnalysisReadingClinicalDataInDto> opt)
@ -267,7 +272,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ClinicalAnswer/SubmitClinicalFormAndSign")] [HttpPost, Route("Inspection/ClinicalAnswer/SubmitClinicalFormAndSign")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SubmitClinicalFormAndSign(DataInspectionDto<SubmitClinicalFormInDto> opt) public async Task<IResponseOutput> SubmitClinicalFormAndSign(DataInspectionDto<SubmitClinicalFormInDto> opt)
@ -284,7 +289,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")] [HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SubmitJudgeVisitTaskResult(DataInspectionDto<SaveJudgeVisitTaskResult> opt) public async Task<IResponseOutput> SubmitJudgeVisitTaskResult(DataInspectionDto<SaveJudgeVisitTaskResult> opt)
@ -303,12 +308,12 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")] [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")]
[UnitOfWork] [UnitOfWork]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
public async Task<IResponseOutput> ConfigTrialBasicInfoConfirm(DataInspectionDto<BasicTrialConfig> opt) public async Task<IResponseOutput> ConfigTrialBasicInfoConfirm(DataInspectionDto<BasicTrialConfig> opt)
{ {
opt.Data.IsTrialBasicLogicConfirmed = true; opt.Data.IsTrialBasicLogicConfirmed = true;
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid= await _inspectionService.RecordSing(opt.SignInfo);
var result = await _trialConfigService.ConfigTrialBasicInfo(opt.Data); var result = await _trialConfigService.ConfigTrialBasicInfo(opt.Data);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
return result; return result;
@ -323,7 +328,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialProcessInfoConfirm")] [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialProcessInfoConfirm")]
[UnitOfWork] [UnitOfWork]
//[TrialGlobalLimit( "BeforeOngoingCantOpt" )] //[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
public async Task<IResponseOutput> ConfigTrialProcessInfoConfirm(DataInspectionDto<TrialProcessConfig> opt) public async Task<IResponseOutput> ConfigTrialProcessInfoConfirm(DataInspectionDto<TrialProcessConfig> opt)
{ {
opt.Data.IsTrialProcessConfirmed = true; opt.Data.IsTrialProcessConfirmed = true;
@ -335,7 +340,7 @@ namespace IRaCIS.Core.API.Controllers
} }
/// <summary> /// <summary>
@ -345,12 +350,12 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")] [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")]
[UnitOfWork] [UnitOfWork]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
public async Task<IResponseOutput> ConfigTrialUrgentInfoConfirm(DataInspectionDto<TrialUrgentConfig> opt) public async Task<IResponseOutput> ConfigTrialUrgentInfoConfirm(DataInspectionDto<TrialUrgentConfig> opt)
{ {
opt.Data.IsTrialUrgentConfirmed = true; opt.Data.IsTrialUrgentConfirmed = true;
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _trialConfigService.ConfigTrialUrgentInfo(opt.Data); var result= await _trialConfigService.ConfigTrialUrgentInfo(opt.Data);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
return result; return result;
} }
@ -358,7 +363,7 @@ namespace IRaCIS.Core.API.Controllers
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")] [HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")]
[UnitOfWork] [UnitOfWork]
[TrialGlobalLimit( "BeforeOngoingCantOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt) public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt)
{ {
opt.Data.IsTrialPACSConfirmed = true; opt.Data.IsTrialPACSConfirmed = true;
@ -374,7 +379,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")] [HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")]
[UnitOfWork] [UnitOfWork]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> TrialConfigSignatureConfirm(DataInspectionDto<SignConfirmDTO> opt) public async Task<IResponseOutput> TrialConfigSignatureConfirm(DataInspectionDto<SignConfirmDTO> opt)
{ {
@ -385,31 +390,31 @@ namespace IRaCIS.Core.API.Controllers
} }
/// <summary> /// <summary>
/// 重置并同步项目阅片标准 /// 重置并同步项目阅片标准
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")] [HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")]
[UnitOfWork] [UnitOfWork]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt) public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt)
{ {
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _trialConfigService.ResetAndAsyncCriterion(opt.Data); var result = await _trialConfigService.ResetAndAsyncCriterion(opt.Data);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
return result; return result;
} }
/// <summary> /// <summary>
/// CRC RequestToQC 批量提交 /// CRC RequestToQC 批量提交
/// </summary> /// </summary>
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")] [HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> CRCRequestToQC(DataInspectionDto<CRCRequestToQCCommand> opt) public async Task<IResponseOutput> CRCRequestToQC(DataInspectionDto<CRCRequestToQCCommand> opt)
{ {
@ -424,12 +429,12 @@ namespace IRaCIS.Core.API.Controllers
/// 设置QC 通过或者不通过 7:QC failed 8QC passed /// 设置QC 通过或者不通过 7:QC failed 8QC passed
/// </summary> /// </summary>
[HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")] [HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> QCPassedOrFailed(DataInspectionDto<QCPassedOrFailedDto> opt) public async Task<IResponseOutput> QCPassedOrFailed(DataInspectionDto<QCPassedOrFailedDto> opt)
{ {
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState); var result= await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
return result; return result;
} }
@ -438,12 +443,12 @@ namespace IRaCIS.Core.API.Controllers
/// 一致性核查 回退 对话记录不清除 只允许PM回退 /// 一致性核查 回退 对话记录不清除 只允许PM回退
/// </summary> /// </summary>
[HttpPost, Route("Inspection/QCOperation/CheckBack")] [HttpPost, Route("Inspection/QCOperation/CheckBack")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> CheckBack(DataInspectionDto<IDDto> opt) public async Task<IResponseOutput> CheckBack(DataInspectionDto<IDDto> opt)
{ {
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _qCOperationService.CheckBack(opt.Data.Id); var result = await _qCOperationService.CheckBack(opt.Data.Id);
await _inspectionService.CompletedSign(singid, result); await _inspectionService.CompletedSign(singid, result);
return result; return result;
} }
@ -455,7 +460,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")] [HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> ReadClinicalDataSign(DataInspectionDto<ReadingClinicalDataSignIndto> opt) public async Task<IResponseOutput> ReadClinicalDataSign(DataInspectionDto<ReadingClinicalDataSignIndto> opt)
{ {
@ -470,9 +475,9 @@ namespace IRaCIS.Core.API.Controllers
/// CRC 设置已经重传完成 /// CRC 设置已经重传完成
/// </summary> /// </summary>
[HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")] [HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt) public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt)
{ {
var singid = await _inspectionService.RecordSing(opt.SignInfo); var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _qCOperationService.SetReuploadFinished(opt.Data); var result = await _qCOperationService.SetReuploadFinished(opt.Data);
@ -486,7 +491,8 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="opt"></param> /// <param name="opt"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/TrialConfig/updateTrialState")] [HttpPost, Route("Inspection/TrialConfig/updateTrialState")]
[TrialGlobalLimit( "BeforeOngoingCantOpt")] //[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> UpdateTrialState(DataInspectionDto<UpdateTrialStateDto> opt) public async Task<IResponseOutput> UpdateTrialState(DataInspectionDto<UpdateTrialStateDto> opt)
{ {
@ -502,7 +508,7 @@ namespace IRaCIS.Core.API.Controllers
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/TrialDocument/userConfirm")] [HttpPost, Route("Inspection/TrialDocument/userConfirm")]
[TrialGlobalLimit( "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> UserConfirm(DataInspectionDto<UserConfirmCommand> opt) public async Task<IResponseOutput> UserConfirm(DataInspectionDto<UserConfirmCommand> opt)
{ {
@ -519,16 +525,16 @@ namespace IRaCIS.Core.API.Controllers
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Inspection/VisitTask/ConfirmReReading")] [HttpPost, Route("Inspection/VisitTask/ConfirmReReading")]
[TrialGlobalLimit( "AfterStopCannNotOpt" )] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt, [FromServices] IVisitTaskService _visitTaskService) public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt , [FromServices] IVisitTaskHelpeService _visitTaskCommonService,[FromServices] IVisitTaskService _visitTaskService)
{ {
var singId = await _inspectionService.RecordSing(opt.SignInfo); var singId = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _visitTaskService.ConfirmReReading(opt.Data); var result = await _visitTaskService.ConfirmReReading(opt.Data, _visitTaskCommonService);
await _inspectionService.CompletedSign(singId, result); await _inspectionService.CompletedSign(singId, result);
return result; return result;
} }
} }
} }

View File

@ -1,7 +1,9 @@
using AutoMapper; using AutoMapper;
using DocumentFormat.OpenXml.Drawing;
using ExcelDataReader; using ExcelDataReader;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces; using IRaCIS.Application.Interfaces;
using IRaCIS.Core.Application.BusinessFilter; using IRaCIS.Core.Application.Auth;
using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Contracts.Dicom; using IRaCIS.Core.Application.Contracts.Dicom;
using IRaCIS.Core.Application.Contracts.Dicom.DTO; using IRaCIS.Core.Application.Contracts.Dicom.DTO;
@ -9,17 +11,20 @@ using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command; using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.Service; using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.Service.ImageAndDoc;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention; using IRaCIS.Core.Infrastructure.Extention;
using MassTransit;
using MassTransit.Mediator; using MassTransit.Mediator;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
@ -35,46 +40,23 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Data; using System.Data;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
using Path = System.IO.Path; using Path = System.IO.Path;
namespace IRaCIS.Core.API.Controllers namespace IRaCIS.Core.API.Controllers
{ {
#region 上传基类封装 #region 上传基类封装
[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)
{
}
}
[DisableFormValueModelBinding] [DisableFormValueModelBinding]
public abstract class UploadBaseController : ControllerBase public abstract class UploadBaseController : ControllerBase
{ {
/// <summary> 流式上传 直接返回</summary> /// <summary> 流式上传 直接返回</summary>
[Route("SingleFileUpload")] [Route("base")]
[ApiExplorerSettings(IgnoreApi = true)]
public virtual async Task<IResponseOutput> SingleFileUploadAsync(Func<string, (string, string)> filePathFunc) public virtual async Task<IResponseOutput> SingleFileUploadAsync(Func<string, (string, string)> filePathFunc)
{ {
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
@ -109,9 +91,7 @@ namespace IRaCIS.Core.API.Controllers
/// <summary> 流式上传 通用封装 不返回任何数据,后续还有事情处理 </summary> /// <summary> 流式上传 通用封装 不返回任何数据,后续还有事情处理 </summary>
[Route("FileUpload")] [Route("base")]
[ApiExplorerSettings(IgnoreApi = true)]
public virtual async Task FileUploadAsync(Func<string, Task<string>> filePathFunc) public virtual async Task FileUploadAsync(Func<string, Task<string>> filePathFunc)
{ {
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
@ -154,9 +134,7 @@ namespace IRaCIS.Core.API.Controllers
} }
} }
[Route("FileUploadToOSS")] [Route("base")]
[ApiExplorerSettings(IgnoreApi = true)]
public virtual async Task FileUploadToOSSAsync(Func<string, Stream, Task<string>> toMemoryStreamFunc) public virtual async Task FileUploadToOSSAsync(Func<string, Stream, Task<string>> toMemoryStreamFunc)
{ {
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
@ -182,10 +160,10 @@ namespace IRaCIS.Core.API.Controllers
} }
/// <summary> 流式上传 Dicom上传 </summary>
[Route("DicomFileUpload")]
[ApiExplorerSettings(IgnoreApi = true)]
/// <summary> 流式上传 Dicom上传 </summary>
[Route("base")]
public virtual async Task DicomFileUploadAsync(Func<string, Stream, int, Task> filePathFunc, string boundary) public virtual async Task DicomFileUploadAsync(Func<string, Stream, int, Task> filePathFunc, string boundary)
{ {
@ -247,42 +225,9 @@ namespace IRaCIS.Core.API.Controllers
#endregion #endregion
#region Dicom 影像上传 临床数据 非diocm #region Dicom 影像上传 临床数据 非diocm
public class UploadNoneDicomFileCommand
{
[NotDefault]
public Guid SubjectVisitId { get; set; }
[ApiExplorerSettings(GroupName = "Image")]
[NotDefault] [ApiController]
public Guid StudyMonitorId { get; set; }
public Guid? NoneDicomStudyId { get; set; }
//IR 上传的时候跟任务绑定
public Guid? VisitTaskId { get; set; }
public string RecordPath { get; set; }
public int FailedFileCount { get; set; }
public long FileSize { get; set; }
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
public class OSSFileDTO
{
public string FilePath { get; set; }
public string FileName { get; set; }
public int FileFize { get; set; }
public string FileType { get; set; }
}
}
[ApiController, ApiExplorerSettings(GroupName = "Image")]
public class StudyController( public class StudyController(
IMediator _mediator, IMediator _mediator,
QCCommon _qCCommon, QCCommon _qCCommon,
@ -293,11 +238,14 @@ namespace IRaCIS.Core.API.Controllers
/// <summary>Dicom 归档</summary> /// <summary>Dicom 归档</summary>
[HttpPost, Route("Study/ArchiveStudy")] [HttpPost, Route("Study/ArchiveStudy")]
[DisableFormValueModelBinding] [DisableFormValueModelBinding]
[DisableRequestSizeLimit] [DisableRequestSizeLimit]
[TrialGlobalLimit("AfterStopCannNotOpt")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> ArchiveStudyNew(Guid trialId, Guid subjectVisitId, string studyInstanceUid, Guid? abandonStudyId, Guid studyMonitorId, public async Task<IResponseOutput> ArchiveStudyNew(Guid trialId, Guid subjectVisitId, string studyInstanceUid, Guid? abandonStudyId, Guid studyMonitorId,
[FromServices] ILogger<UploadDownLoadController> _logger, [FromServices] ILogger<UploadDownLoadController> _logger,
[FromServices] IStudyService _studyService, [FromServices] IStudyService _studyService,
@ -370,7 +318,7 @@ namespace IRaCIS.Core.API.Controllers
//await _uploadHub.Clients.All.ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount); //await _uploadHub.Clients.All.ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount);
await _uploadHub.Clients.User(_userInfo.UserRoleId.ToString()).ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount); await _uploadHub.Clients.User(_userInfo.Id.ToString()).ReceivProgressAsync(archiveStudyCommand.StudyInstanceUid, receivedCount);
archiveResult.ReceivedFileCount = receivedCount; archiveResult.ReceivedFileCount = receivedCount;
@ -444,6 +392,41 @@ namespace IRaCIS.Core.API.Controllers
} }
public class UploadNoneDicomFileCommand
{
[NotDefault]
public Guid SubjectVisitId { get; set; }
[NotDefault]
public Guid StudyMonitorId { get; set; }
public Guid? NoneDicomStudyId { get; set; }
//IR 上传的时候跟任务绑定
public Guid? VisitTaskId { get; set; }
public string RecordPath { get; set; }
public int FailedFileCount { get; set; }
public long FileSize { get; set; }
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
public class OSSFileDTO
{
public string FilePath { get; set; }
public string FileName { get; set; }
public int FileFize { get; set; }
public string FileType { get; set; }
}
}
/// <summary> /// <summary>
/// 非dicom 上传预上传接口 /// 非dicom 上传预上传接口
/// </summary> /// </summary>
@ -452,7 +435,7 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="_studyMonitorRepository"></param> /// <param name="_studyMonitorRepository"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost, Route("Study/PreArchiveStudy")] [HttpPost, Route("Study/PreArchiveStudy")]
[TrialGlobalLimit("AfterStopCannNotOpt")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> PreArchiveStudy(PreArchiveStudyCommand preArchiveStudyCommand, public async Task<IResponseOutput> PreArchiveStudy(PreArchiveStudyCommand preArchiveStudyCommand,
[FromServices] IStudyService _studyService, [FromServices] IStudyService _studyService,
[FromServices] IRepository<StudyMonitor> _studyMonitorRepository) [FromServices] IRepository<StudyMonitor> _studyMonitorRepository)
@ -468,7 +451,7 @@ namespace IRaCIS.Core.API.Controllers
IsSuccess = false, IsSuccess = false,
UploadStartTime = DateTime.Now, UploadStartTime = DateTime.Now,
FileCount = preArchiveStudyCommand.FileCount, FileCount=preArchiveStudyCommand.FileCount,
IsDicom = preArchiveStudyCommand.IsDicom, IsDicom = preArchiveStudyCommand.IsDicom,
IP = _userInfo.IP IP = _userInfo.IP
}; };
@ -488,7 +471,8 @@ namespace IRaCIS.Core.API.Controllers
/// <param name="_noneDicomStudyFileRepository"></param> /// <param name="_noneDicomStudyFileRepository"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("NoneDicomStudy/UploadNoneDicomFile")] [HttpPost("NoneDicomStudy/UploadNoneDicomFile")]
[TrialGlobalLimit("AfterStopCannNotOpt")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> UploadNoneDicomFile(UploadNoneDicomFileCommand incommand, public async Task<IResponseOutput> UploadNoneDicomFile(UploadNoneDicomFileCommand incommand,
[FromServices] IRepository<NoneDicomStudy> _noneDicomStudyRepository, [FromServices] IRepository<NoneDicomStudy> _noneDicomStudyRepository,
[FromServices] IRepository<StudyMonitor> _studyMonitorRepository, [FromServices] IRepository<StudyMonitor> _studyMonitorRepository,
@ -527,7 +511,7 @@ namespace IRaCIS.Core.API.Controllers
} }
var uploadFinishedTime = DateTime.Now; var uploadFinishedTime = DateTime.Now;
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId,true); var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync((t => t.Id == noneDicomStudyId));
noneDicomStudy.FileCount = noneDicomStudy.FileCount + (incommand.VisitTaskId != null ? 0 : incommand.UploadedFileList.Count); noneDicomStudy.FileCount = noneDicomStudy.FileCount + (incommand.VisitTaskId != null ? 0 : incommand.UploadedFileList.Count);
@ -560,7 +544,7 @@ namespace IRaCIS.Core.API.Controllers
/// <returns></returns> /// <returns></returns>
/// <exception cref="BusinessValidationFailedException"></exception> /// <exception cref="BusinessValidationFailedException"></exception>
[HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")] [HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
[TrialGlobalLimit("AfterStopCannNotOpt")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository) public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
{ {
@ -773,13 +757,146 @@ namespace IRaCIS.Core.API.Controllers
#endregion #endregion
#region 医生文件上传下载
#region DTO
public class DoctorDownloadInfo
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ReviewerCode { get; set; }
public List<DownloadFileInfo> FileList { get; set; }
}
public class DownloadFileInfo
{
public string FileName { get; set; }
public string Path { get; set; }
}
public class GetDoctorPathCommand
{
public int Language { get; set; }
public List<Guid> DoctorIdList { get; set; }
}
public class GetDoctoreAttachPathCommand
{
public Guid DoctorId { get; set; }
public List<Guid> AttachmentIdList { get; set; }
}
#endregion
/// <summary>医生文件上传下载</summary>
[ApiExplorerSettings(GroupName = "Common")]
[ApiController]
public class FileController : UploadBaseController
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
private readonly IWebHostEnvironment _hostEnvironment;
private readonly IFileService _fileService;
public FileController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IFileService fileService)
{
_fileService = fileService;
_hostEnvironment = hostEnvironment;
_mapper = mapper;
_userInfo = userInfo;
}
/// <summary>
/// New 医生首页 多选 获取多个医生信息+文件路径列表 医生压缩包名称 doctorCode + "_" + doctorName _(时间戳或者随机的Guid)
/// </summary>
/// <returns></returns>
[HttpPost, Route("file/GetOfficialResume")]
public async Task<IResponseOutput<List<DoctorDownloadInfo>>> GetOfficialResume(GetDoctorPathCommand command,
[FromServices] IRepository<Attachment> _attachmentrepository)
{
var list = await _attachmentrepository.Where(t => command.DoctorIdList.Contains(t.DoctorId) && command.Language == t.Language).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
.Select(g => new DoctorDownloadInfo()
{
Id = g.Key.DoctorId,
Name = g.Key.Name,
ReviewerCode = g.Key.ReviewerCode,
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
}).ToListAsync();
return ResponseOutput.Ok(list);
}
/// <summary>
/// New 项目入组 勾选获取简历路径
/// </summary>
/// <param name="command"></param>
/// <param name="_doctorService"></param>
/// <param name="_attachmentrepository"></param>
/// <returns></returns>
[HttpPost, Route("file/GetTrialDoctorOfficialResume")]
public async Task<IResponseOutput<List<DoctorDownloadInfo>>> GetTrialDoctorOfficialResume(GetDoctorPathCommand command,
[FromServices] IDoctorService _doctorService,
[FromServices] IRepository<Attachment> _attachmentrepository)
{
var list = await _attachmentrepository.Where(t => command.DoctorIdList.Contains(t.DoctorId) && command.Language == t.Language && t.IsOfficial && t.Type.Equals("Resume")).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
.Select(g => new DoctorDownloadInfo()
{
Id = g.Key.DoctorId,
Name = g.Key.Name,
ReviewerCode = g.Key.ReviewerCode,
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
}).ToListAsync();
return ResponseOutput.Ok(list);
}
/// <summary>
/// new 医生详情 勾选或者下载文件路径
/// </summary>
/// <param name="command"></param>
/// <param name="_doctorService"></param>
/// <param name="_attachmentrepository"></param>
/// <returns></returns>
[HttpPost, Route("file/GetDoctorAttachment")]
public async Task<IResponseOutput<DoctorDownloadInfo>> GetDoctorAttachment(GetDoctoreAttachPathCommand command,
[FromServices] IDoctorService _doctorService,
[FromServices] IRepository<Attachment> _attachmentrepository)
{
var find = await _attachmentrepository.Where(t => command.DoctorId == t.DoctorId && command.AttachmentIdList.Contains(t.Id)).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
.Select(g => new DoctorDownloadInfo()
{
Id = g.Key.DoctorId,
Name = g.Key.Name,
ReviewerCode = g.Key.ReviewerCode,
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
}).FirstOrDefaultAsync();
return ResponseOutput.Ok(find);
}
}
#endregion
#region 项目 系统 基本文件 上传 下载 预览 #region 项目 系统 基本文件 上传 下载 预览
[ApiController, ApiExplorerSettings(GroupName = "Common")] [ApiExplorerSettings(GroupName = "Common")]
[ApiController]
public class UploadDownLoadController : UploadBaseController public class UploadDownLoadController : UploadBaseController
{ {
public IMapper _mapper { get; set; } public IMapper _mapper { get; set; }
@ -850,18 +967,11 @@ namespace IRaCIS.Core.API.Controllers
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidCenters"]); throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidCenters"]);
} }
foreach (var item in excelList.GroupBy(t => t.Email.Trim())) if (excelList.GroupBy(t => new { t.TrialSiteCode, t.UserTypeStr, t.Email }).Any(g => g.Count() > 1))
{ {
var itemList = item.ToList(); // 同一邮箱,同一用户类型,只能生成一个账户,请核查Excel数据
var first = item.First(); throw new BusinessValidationFailedException(_localizer["UploadDownLoad_CheckDuplicateAccounts"]);
if (itemList.Any(t => t.Email.Trim() != first.Email.Trim() || t.Phone.Trim() != first.Phone.Trim() || t.OrganizationName.Trim() != first.OrganizationName.Trim() || t.FirstName.Trim() != first.FirstName.Trim() || t.LastName.Trim() != first.LastName.Trim()))
{
//同一邮箱,用户信息应该保持一致!
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_CheckDuplicateAccounts"]);
}
} }
if (excelList.Any(t => !t.Email.Contains("@"))) if (excelList.Any(t => !t.Email.Contains("@")))
@ -886,7 +996,7 @@ namespace IRaCIS.Core.API.Controllers
foreach (var item in excelList) foreach (var item in excelList)
{ {
switch (item.UserTypeStr.Trim().ToUpper()) switch (item.UserTypeStr.ToUpper())
{ {
case "CRC": case "CRC":
@ -903,7 +1013,7 @@ namespace IRaCIS.Core.API.Controllers
} }
item.TrialSiteId = siteList.FirstOrDefault(t => t.TrialSiteCode.Trim().ToUpper() == item.TrialSiteCode.Trim().ToUpper()).TrialSiteId; item.TrialSiteId = siteList.FirstOrDefault(t => t.TrialSiteCode.ToUpper() == item.TrialSiteCode.ToUpper()).TrialSiteId;
} }
var list = excelList.Where(t => t.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator || t.UserTypeEnum == UserTypeEnum.CRA).ToList(); var list = excelList.Where(t => t.UserTypeEnum == UserTypeEnum.ClinicalResearchCoordinator || t.UserTypeEnum == UserTypeEnum.CRA).ToList();

View File

@ -1,84 +0,0 @@
using Microsoft.Extensions.Hosting;
using System.Threading;
using System;
using System.Threading.Tasks;
using MassTransit;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.Extensions.Logging;
using Hangfire;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.Share;
using MassTransit.Scheduling;
using Hangfire.Storage;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using MassTransit.Mediator;
namespace IRaCIS.Core.API.HostService;
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IMediator _mediator,
ILogger<HangfireHostService> _logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("项目启动 hangfire 任务初始化 执行开始~");
//创建邮件定时任务
//项目定时任务都在default 队列
var dbJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Where(t => t.Queue == "default").Select(t => t.Id).ToList();
foreach (var jobId in dbJobIdList)
{
HangfireJobHelper.RemoveCronJob(jobId);
}
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.ToListAsync();
foreach (var task in taskInfoList)
{
//利用主键作为任务Id
var jobId = $"{task.TrialId}({task.TrialCode})_({task.BusinessScenarioEnum})";
var trialId = task.TrialId;
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
}
// 系统邮件定时任务
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
.ToListAsync();
foreach (var task in systemTaskInfoList)
{
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
}
//await _recurringMessageScheduler.ScheduleRecurringPublish(new QCImageQuestionSchedule() { CronExpression = "0/3 * * * * ? " }, new MasstransiTestCommand { value = "message at " + DateTime.Now.ToString() });
_logger.LogInformation("项目启动 hangfire 任务初始化 执行结束");
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

View File

@ -1,30 +0,0 @@
using IRaCIS.Core.Application.MassTransit.Consumer;
using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace IRaCIS.Core.API.HostService
{
public class RecurringJobConfigurationService :
BackgroundService
{
readonly IServiceScopeFactory _scopeFactory;
public RecurringJobConfigurationService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await using var scope = _scopeFactory.CreateAsyncScope();
var endpoint = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();
await endpoint.AddOrUpdateRecurringJob(nameof(MasstransitTestConsumer), new MasstransiTestCommand(), x => x.Every(minutes: 1),
stoppingToken);
}
}
}

View File

@ -38,18 +38,11 @@
<None Remove="GrpcToken.proto" /> <None Remove="GrpcToken.proto" />
<None Remove="IRaCIS.Core.API.xml" /> <None Remove="IRaCIS.Core.API.xml" />
<None Remove="Protos\GrpcToken.proto" /> <None Remove="Protos\GrpcToken.proto" />
<None Remove="Resources\ip2region.xdb" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ApplicationDefinition Include="GrpcToken.proto" /> <ApplicationDefinition Include="GrpcToken.proto" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Resources\ip2region.xdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="wwwroot\swagger\ui\abp.js"> <EmbeddedResource Include="wwwroot\swagger\ui\abp.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
@ -69,23 +62,25 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" /> <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.14" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.15"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="ConfigMapFileProvider" Version="2.0.1" /> <PackageReference Include="ConfigMapFileProvider" Version="2.0.1" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" /> <PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" /> <PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.18" /> <PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" /> <PackageReference Include="Invio.Extensions.Authentication.JwtBearer" Version="2.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="LogDashboard" Version="1.4.8" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
<PackageReference Include="Serilog.Sinks.Email" Version="4.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -105,18 +100,6 @@
<Content Update="appsettings.json"> <Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Update="Resources\en-US.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Resources\zh-CN.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="Resources\GeoLite2-City.mmdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ProjectExtensions> <ProjectExtensions>

View File

@ -16,12 +16,7 @@
医生基本信息 、工作信息 专业信息、审核状态 医生基本信息 、工作信息 专业信息、审核状态
</summary> </summary>
</member> </member>
<member name="M:IRaCIS.Api.Controllers.ExtraController.#ctor(IRaCIS.Application.Interfaces.IAttachmentService,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Application.Interfaces.IEducationService,IRaCIS.Application.Interfaces.ITrialExperienceService,IRaCIS.Application.Interfaces.IResearchPublicationService,IRaCIS.Application.Interfaces.IVacationService)"> <member name="M:IRaCIS.Api.Controllers.ExtraController.GetDoctorDetail(IRaCIS.Application.Interfaces.IAttachmentService,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Application.Interfaces.IEducationService,IRaCIS.Application.Interfaces.ITrialExperienceService,IRaCIS.Application.Interfaces.IResearchPublicationService,IRaCIS.Application.Interfaces.IVacationService,System.Guid)">
<summary>
医生基本信息 、工作信息 专业信息、审核状态
</summary>
</member>
<member name="M:IRaCIS.Api.Controllers.ExtraController.GetDoctorDetail(IRaCIS.Application.Contracts.GetDoctorDetailInDto)">
<summary> <summary>
获取医生详情 获取医生详情
</summary> </summary>
@ -34,21 +29,8 @@
<param name="doctorId"></param> <param name="doctorId"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:IRaCIS.Api.Controllers.ExtraController.OAuthCallBack(System.String,System.String)"> <member name="M:IRaCIS.Api.Controllers.ExtraController.Login(IRaCIS.Application.Contracts.UserLoginDTO,ZiggyCreatures.Caching.Fusion.IFusionCache,IRaCIS.Core.Application.Service.IUserService,IRaCIS.Core.Application.Auth.ITokenService,IRaCIS.Core.Application.Contracts.IReadingImageTaskService,Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.ServiceVerifyConfigOption},Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.SystemEmailSendConfig},IRaCIS.Core.Application.Service.IMailVerificationService)">
<summary> <summary> 系统用户登录接口[New] </summary>
回调到前端,前端调用后端的接口
参考链接https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
后端通过这个code ,带上客户端信息,和授权类型 可以向单点登录提供商获取厂商token
但是单点登录提供商提供的token 和我们系统的token 是有区别的我们的token里面有我们业务系统的UserId涉及到很多业务操作所以在此出现了两种方案
1、前端使用厂商的Token。 后端通过code 获取厂商的Token 返回前端的同时返回我们系统的UserId前段在http 请求头加上一个自定义参数带上UserId 后端取用户Id的地方变动下
但是除了UserId外后端还有其他信息也是从Token取的所以在请求头也需要带上此外后端认证Token的方式也需要变化改造成本稍大如果是微服务做这种处理还是可以的
2、前端还是使用我们后台自己的Token。后端通过code 获取厂商Token的同时后端做一个隐藏登录返回厂商的Token的同时也返回我们系统的Token。
(像我们单体,这种方式最简单,我们用单点登录,无非就是不想记多个系统的密码,自动登录而已,其他不支持的项目改造成本也是最低的)
</summary>
<param name="type">回调的厂商类型 比如github, google, 我们用的logto ,不同的厂商回调到前端的地址可以不同的,但是请求后端的接口可以是同一个 </param>
<param name="code">在第三方平台登录成功后回调前端的时候会返回一个code </param>
<returns></returns>
</member> </member>
<member name="M:IRaCIS.Core.API.Controllers.Special.FinancialChangeController.AddOrUpdateTrialInspection(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Application.Contracts.TrialCommand})"> <member name="M:IRaCIS.Core.API.Controllers.Special.FinancialChangeController.AddOrUpdateTrialInspection(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Application.Contracts.TrialCommand})">
<summary> 添加实验项目-返回新增Id[AUTH]</summary> <summary> 添加实验项目-返回新增Id[AUTH]</summary>
@ -281,7 +263,7 @@
</summary> </summary>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.ConfirmReReading(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.ViewModel.ConfirmReReadingCommand},IRaCIS.Core.Application.Service.IVisitTaskService)"> <member name="M:IRaCIS.Core.API.Controllers.InspectionController.ConfirmReReading(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.ViewModel.ConfirmReReadingCommand},IRaCIS.Core.Application.Service.IVisitTaskHelpeService,IRaCIS.Core.Application.Service.IVisitTaskService)">
<summary> <summary>
重阅同意 重阅同意
</summary> </summary>
@ -308,7 +290,7 @@
<param name="_studyMonitorRepository"></param> <param name="_studyMonitorRepository"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFile(IRaCIS.Core.API.Controllers.UploadNoneDicomFileCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.StudyMonitor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile})"> <member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFile(IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFileCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.StudyMonitor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile})">
<summary> <summary>
上传非Dicom 文件 支持压缩包 多文件上传 上传非Dicom 文件 支持压缩包 多文件上传
</summary> </summary>
@ -328,6 +310,33 @@
<returns></returns> <returns></returns>
<exception cref="T:IRaCIS.Core.Infrastructure.BusinessValidationFailedException"></exception> <exception cref="T:IRaCIS.Core.Infrastructure.BusinessValidationFailedException"></exception>
</member> </member>
<member name="T:IRaCIS.Core.API.Controllers.FileController">
<summary>医生文件上传下载</summary>
</member>
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetOfficialResume(IRaCIS.Core.API.Controllers.GetDoctorPathCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
<summary>
New 医生首页 多选 获取多个医生信息+文件路径列表 医生压缩包名称 doctorCode + "_" + doctorName _(时间戳或者随机的Guid)
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetTrialDoctorOfficialResume(IRaCIS.Core.API.Controllers.GetDoctorPathCommand,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
<summary>
New 项目入组 勾选获取简历路径
</summary>
<param name="command"></param>
<param name="_doctorService"></param>
<param name="_attachmentrepository"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetDoctorAttachment(IRaCIS.Core.API.Controllers.GetDoctoreAttachPathCommand,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
<summary>
new 医生详情 勾选或者下载文件路径
</summary>
<param name="command"></param>
<param name="_doctorService"></param>
<param name="_attachmentrepository"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.UploadDownLoadController.DownloadCommonFile(System.String,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.CommonDocument})"> <member name="M:IRaCIS.Core.API.Controllers.UploadDownLoadController.DownloadCommonFile(System.String,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.CommonDocument})">
<summary> 通用文件下载 </summary> <summary> 通用文件下载 </summary>
</member> </member>
@ -358,10 +367,13 @@
序列化,反序列化的时候,处理时间 时区转换 序列化,反序列化的时候,处理时间 时区转换
</summary> </summary>
</member> </member>
<member name="T:IRaCIS.Core.API.NullToEmptyStringResolver"> <member name="M:IRaCIS.Core.API.NullToEmptyStringResolver.CreateProperties(System.Type,Newtonsoft.Json.MemberSerialization)">
<summary> <summary>
LowerCamelCaseJsonAttribute 可以设置类小写返回给前端 创建属性
</summary> </summary>
<param name="type">类型</param>
<param name="memberSerialization">序列化成员</param>
<returns></returns>
</member> </member>
<member name="T:ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt.CustomHSJWTService"> <member name="T:ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt.CustomHSJWTService">
<summary> <summary>

View File

@ -1,33 +1,43 @@
using IRaCIS.Core.API; using System;
using IRaCIS.Core.API.HostService; using Autofac.Extensions.DependencyInjection;
using IRaCIS.Core.Application.BusinessFilter; using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Application.Filter; using Microsoft.Extensions.Hosting;
using IRaCIS.Core.Application.MassTransit.Consumer; using Microsoft.Extensions.Configuration;
using IRaCIS.Core.Application.Service; using Serilog;
using IRaCIS.Core.Application.Service.BusinessFilter; using System.Threading.Tasks;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using MassTransit; using MassTransit;
using MassTransit.NewIdProviders; using MassTransit.NewIdProviders;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Application.Helper;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder;
using IRaCIS.Core.API;
using Autofac;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using IRaCIS.Core.Application.Filter;
using Microsoft.AspNetCore.HttpOverrides;
using IRaCIS.Core.Application.Service.BackGroundJob;
using LogDashboard;
using FellowOakDicom.Network;
using IRaCIS.Core.Application.Service.ImageAndDoc;
using IP2Region.Net.Abstractions;
using IP2Region.Net.XDB;
using IRaCIS.Core.Application.BusinessFilter;
using Microsoft.AspNetCore.Http;
using IRaCIS.Core.Infrastructure.Extention;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Diagnostics;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.MassTransit.Consumer;
using DocumentFormat.OpenXml.InkML;
using IRaCIS.Core.Domain;
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
#region 获取环境变量 #region 获取环境变量
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数) //以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder() var config = new ConfigurationBuilder()
@ -36,11 +46,6 @@ var config = new ConfigurationBuilder()
var enviromentName = config["ASPNETCORE_ENVIRONMENT"]; var enviromentName = config["ASPNETCORE_ENVIRONMENT"];
var openSwaggerStr = config["ASPNETCORE_OpenSwagger"];
var isOpenSwagger= openSwaggerStr == null|| openSwaggerStr?.ToLower()=="true";
if (string.IsNullOrWhiteSpace(enviromentName)) if (string.IsNullOrWhiteSpace(enviromentName))
{ {
@ -51,15 +56,29 @@ if (string.IsNullOrWhiteSpace(enviromentName))
} }
#endregion #endregion
// Serilog
SerilogExtension.AddSerilogSetup(enviromentName);
var builder = WebApplication.CreateBuilder(new WebApplicationOptions var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{ {
EnvironmentName = enviromentName EnvironmentName = enviromentName
}); });
#region 兼容windows 服务命令行的方式
//foreach (var arg in args)
//{
// Console.WriteLine(arg);
//}
int urlsIndex = Array.FindIndex(args, arg => arg != null && arg.StartsWith("--urls"));
if (urlsIndex > -1)
{
var url = args[urlsIndex].Substring("--urls=".Length);
Console.WriteLine(url);
builder.WebHost.UseUrls(url);
}
#endregion
#region 主机配置 #region 主机配置
@ -68,90 +87,146 @@ NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
builder.Configuration.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), "appsettings.json", false, true) builder.Configuration.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), "appsettings.json", false, true)
.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), $"appsettings.{enviromentName}.json", false, true); .AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), $"appsettings.{enviromentName}.json", false, true);
builder.Host.UseSerilog(); builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<AutofacModuleSetup>();
})
.UseWindowsService().UseSerilog();
#endregion #endregion
#region 配置服务 #region 配置服务
var _configuration = builder.Configuration; var _configuration = builder.Configuration;
//手动注册服务
builder.Services.ConfigureServices(_configuration);
builder.Services.AddHostedService<HangfireHostService>();
//minimal api 异常处理
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//builder.Services.AddProblemDetails();
//健康检查 //健康检查
builder.Services.AddHealthChecks(); builder.Services.AddHealthChecks();
builder.Services.AddSerilog();
//本地化 //本地化
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options => builder.Services.AddControllers(options =>
{ {
//options.Filters.Add<LogActionFilter>();
options.Filters.Add<ModelActionFilter>(); options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>(); options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>(); options.Filters.Add<UnitOfWorkFilter>();
//options.Filters.Add<EncreptApiResultFilter>(10);
options.Filters.Add<LimitUserRequestAuthorization>(); options.Filters.Add<LimitUserRequestAuthorization>();
options.Filters.Add<TrialGlobalLimitActionFilter>();
}) })
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理 .AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
// Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码 builder.Services.AddOptions().Configure<SystemEmailSendConfig>(_configuration.GetSection("SystemEmailSendConfig"));
builder.Services.AddDynamicWebApiSetup(); builder.Services.AddOptions().Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
//MinimalAPI builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
builder.Services.AddMasaMinimalAPiSetUp(); builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
builder.Services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
builder.Services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
builder.Services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
builder.Services.AddDynamicWebApiSetup();
//AutoMapper //AutoMapper
builder.Services.AddAutoMapperSetup(); builder.Services.AddAutoMapperSetup();
//EF ORM QueryWithNoLock //EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration, enviromentName); builder.Services.AddEFSetup(_configuration, enviromentName);
//Http 响应压缩 //Http 响应压缩
builder.Services.AddResponseCompressionSetup(); builder.Services.AddResponseCompressionSetup();
//Swagger Api 文档
if (isOpenSwagger) builder.Services.AddSwaggerSetup();
{
//Swagger Api 文档
builder.Services.AddSwaggerSetup();
}
//JWT Token 验证 //JWT Token 验证
builder.Services.AddJWTAuthSetup(_configuration); builder.Services.AddJWTAuthSetup(_configuration);
//MassTransit // MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
builder.Services.AddMassTransitSetup(); //builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
#region MassTransit
//masstransit组件 也支持MediatR 中介者模式但是支持分布式考虑后续所以在次替代MediatR
//参考链接https://masstransit.io/documentation/concepts/mediator#scoped-mediator
builder.Services.AddMediator(cfg =>
{
cfg.AddConsumer<ConsistencyCheckConsumer>();
cfg.AddConsumer<AddSubjectTriggerConsumer>();
cfg.AddConsumer<AddSubjectTriggerConsumer2>();
//cfg.ConfigureMediator((context, cfg) => cfg.UseHttpContextScopeFilter(context));
});
//添加 MassTransit 和 InMemory 传输
builder.Services.AddMassTransit(cfg =>
{
// 注册消费者
cfg.AddConsumer<AddSubjectTriggerConsumer>(); // 替换为你的消费者类
cfg.AddConsumer<AddSubjectTriggerConsumer2>();
// 使用 InMemory 作为消息传递机制
cfg.UsingInMemory((context, cfg) =>
{
// 这里可以进行额外的配置
cfg.ConfigureEndpoints(context); // 自动配置所有消费者的端点
});
});
#endregion
#region FusionCache
// FusionCache
builder.Services.AddFusionCache(); builder.Services.AddFusionCache();
#endregion
// EasyCaching 缓存
//builder.Services.AddEasyCachingSetup(_configuration);
// hangfire 定时任务框架 有界面,更友好~ // hangfire 定时任务框架 有界面,更友好~
builder.Services.AddhangfireSetup(_configuration); builder.Services.AddhangfireSetup(_configuration);
//Serilog 日志可视化 LogDashboard日志
//builder.Services.AddLogDashboardSetup();
//Serilog 日志可视化 LogDashboard日志
builder.Services.AddLogDashboardSetup();
builder.Services.AddJsonConfigSetup(_configuration);
//转发头设置 获取真实IP
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
//Dicom影像渲染图片 跨平台 //Dicom影像渲染图片 跨平台
builder.Services.AddDicomSetup(); builder.Services.AddDicomSetup();
// 实时应用 // 实时应用
builder.Services.AddSignalR(); builder.Services.AddSignalR();
builder.Services.AddSingleton<IUserIdProvider, IRaCISUserIdProvider>();
//// 添加反伪造服务
//builder.Services.AddAntiforgery(options => builder.Services.AddSingleton<ISearcher>(new Searcher(CachePolicy.Content, Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources, "ip2region.xdb")));
//builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//builder.Services.AddProblemDetails();
#region 历史废弃配置
//builder.Services.AddMemoryCache();
////上传限制 配置
//builder.Services.Configure<FormOptions>(options =>
//{ //{
// // 可选:设置自定义的头部名称以支持 AJAX 请求等 // options.MultipartBodyLengthLimit = int.MaxValue;
// options.HeaderName = "X-XSRF-TOKEN"; // options.ValueCountLimit = int.MaxValue;
// options.ValueLengthLimit = int.MaxValue;
//}); //});
//IP 限流 可设置白名单 或者黑名单
//builder.Services.AddAntiforgery(); //services.AddIpPolicyRateLimitSetup(_configuration);
// 用户类型 策略授权
//services.AddAuthorizationPolicySetup(_configuration);
#endregion
#endregion #endregion
@ -160,9 +235,10 @@ var env = app.Environment;
#region 配置中间件 #region 配置中间件
app.UseMiddleware<EncryptionRequestMiddleware>(); app.UseMiddleware<EncryptionRequestMiddleware>();
// Configure the HTTP request pipeline.
#region 异常处理 全局业务异常已统一处理了,非业务错误会来到这里 400 -500状态码 #region 异常处理 全局业务异常已统一处理了,非业务错误会来到这里 400 -500状态码
@ -184,8 +260,54 @@ app.UseStatusCodePages(async context =>
}); });
//app.UseExceptionHandler(); //app.UseExceptionHandler(o => { });
app.UseExceptionHandler(o => { });
//这里没生效原因未知官方文档也是这种写法也用了GlobalExceptionHandler 尝试还是不行怀疑框架bug
//app.UseExceptionHandler(configure =>
//{
// configure.Run(async context =>
// {
// var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
//var ex = exceptionHandlerPathFeature?.Error;
//context.Response.ContentType = "application/json";
//if (ex != null)
//{
// var errorInfo = $"Exception: {ex.Message}[{ex.StackTrace}]" + (ex.InnerException != null ? $" InnerException: {ex.InnerException.Message}[{ex.InnerException.StackTrace}]" : "");
// await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk($"{ex?.Message}")));
// Log.Logger.Error(errorInfo);
//}
// });
//});
#endregion
//本地化
app.UseLocalization();
app.UseForwardedHeaders();
//响应压缩
app.UseResponseCompression();
//不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication.
app.UseStaticFiles();
//LogDashboard
app.UseLogDashboard("/LogDashboard");
//hangfire
app.UseHangfireConfig(env);
#region 暂时废弃 #region 暂时废弃
@ -200,66 +322,46 @@ app.UseExceptionHandler(o => { });
//{ //{
// //app.UseHsts(); // //app.UseHsts();
//} //}
//app.UseIRacisHostStaticFileStore(env);
#endregion #endregion
#endregion
app.UseIRacisHostStaticFileStore(env);
//本地化
await app.UseLocalization(app.Services);
app.UseForwardedHeaders();
//响应压缩
app.UseResponseCompression();
//不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication.
app.UseStaticFiles();
//LogDashboard
//app.UseLogDashboard("/LogDashboard");
//hangfire
app.UseHangfireConfig(env);
// Swagger
if (isOpenSwagger)
{
SwaggerSetup.Configure(app, env);
}
//serilog 记录请求的用户信息
SwaggerSetup.Configure(app, env);
////serilog 记录请求的用户信息
app.UseSerilogConfig(env); app.UseSerilogConfig(env);
app.UseRouting(); app.UseRouting();
app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
//app.UseIRacisHostStaticFileStore(env);
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
//Map MinimalAPI routes
app.MapMasaMinimalAPIs();
//// 这里添加反伪造中间件
//app.UseAntiforgery();
app.MapControllers(); app.MapControllers();
app.MapHub<UploadHub>("/UploadHub"); app.MapHub<UploadHub>("/UploadHub");
app.MapHealthChecks("/health"); app.MapHealthChecks("/health");
// Serilog
SerilogExtension.AddSerilogSetup(enviromentName, app.Services);
var hangfireJobService = app.Services.GetRequiredService<IIRaCISHangfireJob>();
await hangfireJobService.InitHangfireJobTaskAsync();
#endregion #endregion
try try
{ {
#region 运行环境 部署平台 #region 运行环境 部署平台
@ -279,6 +381,9 @@ try
Log.Logger.Warning($"当前部署平台环境OSX or FreeBSD"); Log.Logger.Warning($"当前部署平台环境OSX or FreeBSD");
} }
#endregion
Log.Logger.Warning($"ContentRootPath{env.ContentRootPath}"); Log.Logger.Warning($"ContentRootPath{env.ContentRootPath}");
@ -289,11 +394,10 @@ try
//Log.Logger.Warning($"ContentRootPath——GetParent{Directory.GetParent(env.ContentRootPath).Parent.FullName}"); //Log.Logger.Warning($"ContentRootPath——GetParent{Directory.GetParent(env.ContentRootPath).Parent.FullName}");
//Log.Logger.Warning($"ContentRootPath——xx{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}"); //Log.Logger.Warning($"ContentRootPath——xx{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}");
#endregion
app.Run(); app.Run();
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -20,8 +20,7 @@
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test_IRC", "ASPNETCORE_ENVIRONMENT": "Test_IRC"
"ASPNETCORE_OpenSwagger": "true"
}, },
"applicationUrl": "http://localhost:6100" "applicationUrl": "http://localhost:6100"
}, },
@ -70,14 +69,6 @@
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC" "ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
}, },
"applicationUrl": "http://localhost:6100" "applicationUrl": "http://localhost:6100"
},
"IRaCIS.US_Prod_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 MiB

View File

@ -17,7 +17,7 @@ namespace IRaCIS.Core.API
{ {
public virtual string GetUserId(HubConnectionContext connection) public virtual string GetUserId(HubConnectionContext connection)
{ {
return connection.User?.FindFirst(JwtIRaCISClaimType.IdentityUserId)?.Value!; return connection.User?.FindFirst(JwtIRaCISClaimType.Id)?.Value!;
} }
} }
@ -38,7 +38,7 @@ namespace IRaCIS.Core.API
public override Task OnConnectedAsync() public override Task OnConnectedAsync()
{ {
//base.Context.User.id //base.Context.User.id
_logger.LogError("连接: " + Context.ConnectionId); _logger.LogError("连接: " + Context.ConnectionId);

View File

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace IRaCIS.Core.API.Filter
{
public class EnableBufferingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableBuffering();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}

View File

@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
namespace IRaCIS.Core.API.Utility
{
public static class FileHelpers
{
private static readonly byte[] _allowedChars = { };
// For more file signatures, see the File Signatures Database (https://www.filesignatures.net/)
// and the official specifications for the file types you wish to add.
private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
{
{ ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
{ ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
{ ".jpg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
}
},
{ ".zip", new List<byte[]>
{
new byte[] { 0x50, 0x4B, 0x03, 0x04 },
new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
new byte[] { 0x50, 0x4B, 0x05, 0x06 },
new byte[] { 0x50, 0x4B, 0x07, 0x08 },
new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
}
},
};
// **WARNING!**
// In the following file processing methods, the file's content isn't scanned.
// In most production scenarios, an anti-virus/anti-malware scanner API is
// used on the file before making the file available to users or other
// systems. For more information, see the topic that accompanies this sample
// app.
public static async Task<byte[]> ProcessFormFile<T>(IFormFile formFile,
ModelStateDictionary modelState, string[] permittedExtensions,
long sizeLimit)
{
var fieldDisplayName = string.Empty;
// Use reflection to obtain the display name for the model
// property associated with this IFormFile. If a display
// name isn't found, error messages simply won't show
// a display name.
MemberInfo property =
typeof(T).GetProperty(
formFile.Name.Substring(formFile.Name.IndexOf(".",
StringComparison.Ordinal) + 1));
if (property != null)
{
if (property.GetCustomAttribute(typeof(DisplayAttribute)) is
DisplayAttribute displayAttribute)
{
fieldDisplayName = $"{displayAttribute.Name} ";
}
}
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
formFile.FileName);
// Check the file length. This check doesn't catch files that only have
// a BOM as their content.
if (formFile.Length == 0)
{
modelState.AddModelError(formFile.Name,
$"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
return new byte[0];
}
if (formFile.Length > sizeLimit)
{
var megabyteSizeLimit = sizeLimit / 1048576;
modelState.AddModelError(formFile.Name,
$"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
$"{megabyteSizeLimit:N1} MB.");
return new byte[0];
}
try
{
using (var memoryStream = new MemoryStream())
{
await formFile.CopyToAsync(memoryStream);
// Check the content length in case the file's only
// content was a BOM and the content is actually
// empty after removing the BOM.
if (memoryStream.Length == 0)
{
modelState.AddModelError(formFile.Name,
$"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
}
if (!IsValidFileExtensionAndSignature(
formFile.FileName, memoryStream, permittedExtensions))
{
modelState.AddModelError(formFile.Name,
$"{fieldDisplayName}({trustedFileNameForDisplay}) file " +
"type isn't permitted or the file's signature " +
"doesn't match the file's extension.");
}
else
{
return memoryStream.ToArray();
}
}
}
catch (Exception ex)
{
modelState.AddModelError(formFile.Name,
$"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
$"Please contact the Help Desk for support. Error: {ex.HResult}");
}
return new byte[0];
}
public static async Task<byte[]> ProcessStreamedFile(
MultipartSection section, ContentDispositionHeaderValue contentDisposition,
ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit)
{
try
{
using (var memoryStream = new MemoryStream())
{
await section.Body.CopyToAsync(memoryStream);
// Check if the file is empty or exceeds the size limit.
if (memoryStream.Length == 0)
{
modelState.AddModelError("File", "The file is empty.");
}
else if (memoryStream.Length > sizeLimit)
{
var megabyteSizeLimit = sizeLimit / 1048576;
modelState.AddModelError("File",
$"The file exceeds {megabyteSizeLimit:N1} MB.");
}
else if (!IsValidFileExtensionAndSignature(
contentDisposition.FileName.Value, memoryStream,
permittedExtensions))
{
modelState.AddModelError("File",
"The file type isn't permitted or the file's " +
"signature doesn't match the file's extension.");
}
else
{
return memoryStream.ToArray();
}
}
}
catch (Exception ex)
{
modelState.AddModelError("File",
"The upload failed. Please contact the Help Desk " +
$" for support. Error: {ex.HResult}");
// Log the exception
}
return new byte[0];
}
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions)
{
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
{
return false;
}
var ext = Path.GetExtension(fileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
return false;
}
data.Position = 0;
using (var reader = new BinaryReader(data))
{
if (ext.Equals(".txt") || ext.Equals(".csv") || ext.Equals(".prn"))
{
if (_allowedChars.Length == 0)
{
// Limits characters to ASCII encoding.
for (var i = 0; i < data.Length; i++)
{
if (reader.ReadByte() > sbyte.MaxValue)
{
return false;
}
}
}
else
{
// Limits characters to ASCII encoding and
// values of the _allowedChars array.
for (var i = 0; i < data.Length; i++)
{
var b = reader.ReadByte();
if (b > sbyte.MaxValue ||
!_allowedChars.Contains(b))
{
return false;
}
}
}
return true;
}
// Uncomment the following code block if you must permit
// files whose signature isn't provided in the _fileSignature
// dictionary. We recommend that you add file signatures
// for files (when possible) for all file types you intend
// to allow on the system and perform the file signature
// check.
//if (!_fileSignature.ContainsKey(ext))
//{
// return true;
//}
// File signature check
// --------------------
// With the file signatures provided in the _fileSignature
// dictionary, the following code tests the input content's
// file signature.
//var signatures = _fileSignature[ext];
//var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
//return signatures.Any(signature =>
// headerBytes.Take(signature.Length).SequenceEqual(signature));
//test
return true;
}
}
}
}

View File

@ -1,9 +1,12 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System; using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
{ {

View File

@ -1,10 +1,13 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System; using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading.Tasks;
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
{ {

View File

@ -1,4 +1,9 @@
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
{ {
public interface ICustomJWTService public interface ICustomJWTService
{ {

View File

@ -1,4 +1,9 @@
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
{ {
public class JWTTokenOptions public class JWTTokenOptions
{ {

View File

@ -1,6 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading.Tasks;
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
{ {

View File

@ -0,0 +1,54 @@
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace IRaCIS.Core.API.Utility
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException(
"Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}

View File

@ -1,6 +1,9 @@
using Hangfire; using Hangfire;
using Hangfire.Dashboard; using Hangfire.Dashboard;
using Hangfire.Dashboard.BasicAuthorization; using Hangfire.Dashboard.BasicAuthorization;
using IRaCIS.Core.Application.Service.BackGroundJob;
using IRaCIS.Core.API.Filter;
using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -36,7 +39,7 @@ namespace IRaCIS.Core.API
}) })
}, },
DashboardTitle = "后台任务管理", DashboardTitle ="后台任务管理",
//Authorization = new BasicAuthAuthorizationFilter[] { //Authorization = new BasicAuthAuthorizationFilter[] {

View File

@ -1,12 +1,21 @@
using IRaCIS.Core.Application.Helper; using Azure;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention; using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualBasic;
using Newtonsoft.Json; using Newtonsoft.Json;
using SharpCompress.Common;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -23,7 +32,7 @@ namespace IRaCIS.Core.API
private string iRaCISDefaultDataFolder = string.Empty; private string iRaCISDefaultDataFolder = string.Empty;
public MultiDiskStaticFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, ILoggerFactory loggerFactory) public MultiDiskStaticFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, ILoggerFactory loggerFactory)
{ {
_next = next; _next = next;
_hostingEnv = hostingEnv; _hostingEnv = hostingEnv;
@ -80,7 +89,7 @@ namespace IRaCIS.Core.API
if (defaultFileProvider.GetFileInfo(path).Exists) if (defaultFileProvider.GetFileInfo(path).Exists)
{ {
var actrualPath = defaultFileProvider.GetFileInfo(path).PhysicalPath; var actrualPath = defaultFileProvider.GetFileInfo(path).PhysicalPath;
await context.Response.SendFileAsync(new PhysicalFileInfo(new FileInfo(actrualPath))); await context.Response.SendFileAsync(new PhysicalFileInfo(new FileInfo(actrualPath)));
@ -101,7 +110,7 @@ namespace IRaCIS.Core.API
.Where(d => d.IsReady && d.DriveType == DriveType.Fixed)/*.Where(t => !t.Name.Contains("C") && !t.Name.Contains("c"))*/ .Where(d => d.IsReady && d.DriveType == DriveType.Fixed)/*.Where(t => !t.Name.Contains("C") && !t.Name.Contains("c"))*/
.OrderBy(d => d.AvailableFreeSpace) .OrderBy(d => d.AvailableFreeSpace)
.Select(d => d.RootDirectory.FullName) .Select(d => d.RootDirectory.FullName)
.ToArray().Where(t => !t.Contains(defaultRoot)); .ToArray().Where(t=>!t.Contains(defaultRoot));
foreach (var item in disks) foreach (var item in disks)
@ -112,10 +121,10 @@ namespace IRaCIS.Core.API
{ {
continue; continue;
} }
var otherFileProvider= new PhysicalFileProvider(otherFileStoreFolder);
var otherFileProvider = new PhysicalFileProvider(otherFileStoreFolder);
if (otherFileProvider.GetFileInfo(path).Exists) if (otherFileProvider.GetFileInfo(path).Exists)
{ {
@ -123,7 +132,7 @@ namespace IRaCIS.Core.API
var actrualPath = otherFileProvider.GetFileInfo(path).PhysicalPath; var actrualPath = otherFileProvider.GetFileInfo(path).PhysicalPath;
//方式一 //方式一
await context.Response.SendFileAsync(new PhysicalFileInfo(new FileInfo(actrualPath))); await context.Response.SendFileAsync( new PhysicalFileInfo(new FileInfo(actrualPath)));
#region 方式二 报错 otherFileProvider 应该还包含/{StaticData.Folder.IRaCISDataFolder} 这一层级 #region 方式二 报错 otherFileProvider 应该还包含/{StaticData.Folder.IRaCISDataFolder} 这一层级
//var otherStaticFileOptions = new StaticFileOptions //var otherStaticFileOptions = new StaticFileOptions
@ -149,7 +158,7 @@ namespace IRaCIS.Core.API
await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk("File not found"))); await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk("File not found")));
} }
// 如果所有磁盘都不存在所请求的文件,则将请求传递给下一个中间件组件。 // 如果所有磁盘都不存在所请求的文件,则将请求传递给下一个中间件组件。
await _next.Invoke(context); await _next.Invoke(context);

View File

@ -1,28 +1,19 @@
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
public static class LocalizationConfig public static class LocalizationConfig
{ {
public static async Task UseLocalization(this IApplicationBuilder app,IServiceProvider serviceProvider) public static void UseLocalization(this IApplicationBuilder app)
{ {
var supportedCultures = new List<CultureInfo> var supportedCultures = new List<CultureInfo>
{ {
new CultureInfo(StaticData.CultureInfo.en_US), new CultureInfo(StaticData.CultureInfo.en_US),
new CultureInfo(StaticData.CultureInfo.zh_CN) new CultureInfo(StaticData.CultureInfo.zh_CN)
}; };
@ -40,28 +31,6 @@ namespace IRaCIS.Core.API
//options.RequestCultureProviders.RemoveAt(1); //options.RequestCultureProviders.RemoveAt(1);
app.UseRequestLocalization(options); app.UseRequestLocalization(options);
//设置国际化I18n
var localizer = serviceProvider.GetRequiredService<IStringLocalizer>();
I18n.SetLocalizer(localizer);
//初始化国际化
var _internationalizationRepository = serviceProvider.GetRequiredService<IRepository<Internationalization>>();
//查询数据库的数据
var toJsonList = await _internationalizationRepository.Where(t => t.InternationalizationType == 1).Select(t => new IRCGlobalInfoDTO()
{
Code = t.Code,
Value = t.Value,
ValueCN = t.ValueCN,
Description = t.Description
}).ToListAsync();
await InternationalizationHelper.BatchAddJsonKeyValueAsync(toJsonList);
} }
} }
} }

View File

@ -1,15 +1,15 @@
//using LogDashboard; using LogDashboard;
//using LogDashboard.Authorization; using LogDashboard.Authorization;
//namespace IRaCIS.Core.API.Filter namespace IRaCIS.Core.API.Filter
//{ {
// public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter
// { {
// //在此可以利用 本系统的UerTypeEnum 判断 //在此可以利用 本系统的UerTypeEnum 判断
// public bool Authorization(LogDashboardContext context) public bool Authorization(LogDashboardContext context)
// { {
// return context.HttpContext.User.Identity.IsAuthenticated; return context.HttpContext.User.Identity.IsAuthenticated;
// } }
// } }
//} }

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Serilog;
using Serilog.Context; using Serilog.Context;
using System; using System;
using System.IO; using System.IO;

View File

@ -1,14 +1,13 @@
using IRaCIS.Core.API._PipelineExtensions.Serilog; using Hangfire;
using IRaCIS.Core.Domain.Share; using Hangfire.Dashboard;
using IRaCIS.Core.API._PipelineExtensions.Serilog;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Serilog; using Serilog;
using System.Linq;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
public static class SerilogConfig public static class SerilogConfig
{ {
@ -20,66 +19,8 @@ namespace IRaCIS.Core.API
app.UseSerilogRequestLogging(opts app.UseSerilogRequestLogging(opts
=> =>
{ {
opts.MessageTemplate = "{TokenUserRealName} {TokenUserTypeShortName} {ClientIp} {LocalIP} {Host} {Protocol} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms";
opts.MessageTemplate = "{FullName} {UserType} {UserIp} {Host} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms"; opts.EnrichDiagnosticContext = SerilogHelper.EnrichFromRequest;
opts.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
var request = httpContext.Request;
// Set all the common properties available for every request
diagnosticContext.Set("Host", request.Host.Value);
// Only set it if available. You're not sending sensitive data in a querystring right?!
if (request.QueryString.HasValue)
{
diagnosticContext.Set("QueryString", request.QueryString.Value);
}
diagnosticContext.Set("FullName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.FullName)?.Value);
diagnosticContext.Set("UserType", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value);
var clientIp = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault() ??
httpContext.Connection.RemoteIpAddress?.ToString();
if (clientIp.StartsWith("::ffff:"))
{
clientIp = clientIp.Substring(7); // 移除前缀
}
diagnosticContext.Set("UserIp", clientIp);
#region 非必要不记录
//diagnosticContext.Set("Protocol", request.Protocol);
//diagnosticContext.Set("Scheme", request.Scheme);
//// Retrieve the IEndpointFeature selected for the request
//var endpoint = httpContext.GetEndpoint();
//if (endpoint is object) // endpoint != null
//{
// diagnosticContext.Set("EndpointName", endpoint.DisplayName);
//}
// Set the content-type of the Response at this point
//diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
#endregion
#region old 未用
//这种获取的Ip不准 配置服务才行
//diagnosticContext.Set("RequestIP", httpContext.Connection.RemoteIpAddress.ToString());
//这种方式可以但是serilog提供了 就不用了
//diagnosticContext.Set("TestIP", httpContext.GetUserIp());
//这种方式不行 读取的body为空字符串 必须在中间件中读取
//diagnosticContext.Set("RequestBody", await ReadRequestBody(httpContext.Request));
//diagnosticContext.Set("RequestBody", RequestPayload);
#endregion
};
}); });

View File

@ -0,0 +1,61 @@
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Http;
using Serilog;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.API
{
public class SerilogHelper
{
//public static string RequestPayload = "";
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
var request = httpContext.Request;
// Set all the common properties available for every request
diagnosticContext.Set("Host", request.Host);
diagnosticContext.Set("Protocol", request.Protocol);
diagnosticContext.Set("Scheme", request.Scheme);
#region old 未用
//这种获取的Ip不准 配置服务才行
//diagnosticContext.Set("RequestIP", httpContext.Connection.RemoteIpAddress.ToString());
//这种方式可以但是serilog提供了 就不用了
//diagnosticContext.Set("TestIP", httpContext.GetUserIp());
//这种方式不行 读取的body为空字符串 必须在中间件中读取
//diagnosticContext.Set("RequestBody", await ReadRequestBody(httpContext.Request));
//diagnosticContext.Set("RequestBody", RequestPayload);
#endregion
// Only set it if available. You're not sending sensitive data in a querystring right?!
if (request.QueryString.HasValue)
{
diagnosticContext.Set("QueryString", request.QueryString.Value);
}
// Set the content-type of the Response at this point
diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
diagnosticContext.Set("TokenUserRealName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.RealName)?.Value);
diagnosticContext.Set("TokenUserTypeShortName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value);
// Retrieve the IEndpointFeature selected for the request
var endpoint = httpContext.GetEndpoint();
if (endpoint is object) // endpoint != null
{
diagnosticContext.Set("EndpointName", endpoint.DisplayName);
}
}
}
}

View File

@ -14,13 +14,13 @@ namespace IRaCIS.Core.API
public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions> public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{ {
public IStringLocalizer _localizer; public IStringLocalizer _localizer;
public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
IStringLocalizer localizer, IStringLocalizer localizer,
ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
{ {
_localizer = localizer; _localizer = localizer;
} }
protected override Task<AuthenticateResult> HandleAuthenticateAsync() protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
@ -30,7 +30,7 @@ namespace IRaCIS.Core.API
{ {
Response.ContentType = "application/json"; Response.ContentType = "application/json";
Response.StatusCode = StatusCodes.Status401Unauthorized; Response.StatusCode = StatusCodes.Status401Unauthorized;
//---您无权访问该接口 //---您无权访问该接口
await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_NoAccess"], ApiResponseCodeEnum.NoToken))); await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_NoAccess"], ApiResponseCodeEnum.NoToken)));
} }
@ -38,8 +38,8 @@ namespace IRaCIS.Core.API
{ {
Response.ContentType = "application/json"; Response.ContentType = "application/json";
Response.StatusCode = StatusCodes.Status403Forbidden; Response.StatusCode = StatusCodes.Status403Forbidden;
//---您的权限不允许进行该操作 //---您的权限不允许进行该操作
await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_Permission"], ApiResponseCodeEnum.HaveTokenNotAccess))); await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_Permission"],ApiResponseCodeEnum.HaveTokenNotAccess)));
} }
} }

View File

@ -34,7 +34,7 @@ namespace IRaCIS.Core.API
{ {
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.IQC).ToString()); policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.IQC).ToString());
}); });
options.AddPolicy(IRaCISPolicy.PM, policyBuilder => options.AddPolicy(IRaCISPolicy.PM, policyBuilder =>
{ {
@ -54,7 +54,7 @@ namespace IRaCIS.Core.API
options.AddPolicy(IRaCISPolicy.PM_APM_CRC_QC, policyBuilder => options.AddPolicy(IRaCISPolicy.PM_APM_CRC_QC, policyBuilder =>
{ {
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.IQC).ToString()); policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(),((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.IQC).ToString());
}); });
options.AddPolicy(IRaCISPolicy.SPM_CPM, policyBuilder => options.AddPolicy(IRaCISPolicy.SPM_CPM, policyBuilder =>
@ -64,23 +64,23 @@ namespace IRaCIS.Core.API
options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM, policyBuilder => options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM, policyBuilder =>
{ {
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString()); policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(),((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString());
}); });
options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM_SMM_CMM, policyBuilder => options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM_SMM_CMM, policyBuilder =>
{ {
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(), policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(),
((int)UserTypeEnum.CPM).ToString(), ((int)UserTypeEnum.SMM).ToString(), ((int)UserTypeEnum.CMM).ToString()); ((int)UserTypeEnum.CPM).ToString(), ((int)UserTypeEnum.SMM).ToString(), ((int)UserTypeEnum.CMM).ToString());
}); });
}); });
} }
} }
} }

View File

@ -45,7 +45,7 @@ namespace IRaCIS.Core.API
{ {
OnMessageReceived = (context) => OnMessageReceived = (context) =>
{ {
if (context.Request.Query.TryGetValue("access_token", out StringValues values)) if (context.Request.Query.TryGetValue("access_token", out StringValues values))
{ {
var queryToken = values.FirstOrDefault(); var queryToken = values.FirstOrDefault();
@ -58,10 +58,10 @@ namespace IRaCIS.Core.API
} }
} }
//仅仅是访问文件的时候才会去取token认证 前端对cookie设置了有效期 //仅仅是访问文件的时候才会去取token认证 前端对cookie设置了有效期
if (context.Request.Path.ToString().Contains("IRaCISData") || context.Request.Path.ToString().Contains("SystemData")) if (context.Request.Path.ToString().Contains("IRaCISData") || context.Request.Path.ToString().Contains("SystemData") )
{ {
var cookieToken = context.Request.Cookies["access_token"]; var cookieToken = context.Request.Cookies["access_token"];

View File

@ -1,4 +1,6 @@
using Autofac; using Autofac;
using IRaCIS.Core.Application;
using IRaCIS.Core.Application.BackGroundJob;
using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Panda.DynamicWebApi; using Panda.DynamicWebApi;
@ -8,10 +10,10 @@ using System.Reflection;
using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Service; using IRaCIS.Core.Application.Service;
using IRaCIS.Application.Interfaces;
using AutoMapper; using AutoMapper;
using IRaCIS.Core.SCP.Service;
namespace IRaCIS.Core.SCP namespace IRaCIS.Core.API
{ {
// ReSharper disable once IdentifierTypo // ReSharper disable once IdentifierTypo
public class AutofacModuleSetup : Autofac.Module public class AutofacModuleSetup : Autofac.Module
@ -36,19 +38,28 @@ namespace IRaCIS.Core.SCP
.Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type)) .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
.PropertiesAutowired(); .PropertiesAutowired();
#endregion #endregion
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + typeof(BaseService).Assembly.GetName().Name+".dll");
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "IRaCIS.Core.Application.dll");
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service")) containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
.PropertiesAutowired().AsImplementedInterfaces(); .PropertiesAutowired().AsImplementedInterfaces();
//containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance(); //containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
//containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope(); //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
//注册hangfire任务 依赖注入
containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();

View File

@ -1,14 +1,9 @@
using IRaCIS.Core.Application.BusinessFilter; using Microsoft.Extensions.DependencyInjection;
using IRaCIS.Core.Application.Service.BusinessFilter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Panda.DynamicWebApi; using Panda.DynamicWebApi;
using System.Collections.Generic;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
public static class WebApiSetup public static class DynamicWebApiSetup
{ {
//20210910 避免冗余的控制器层代码编写,仅仅包了一层前后台定义的格式 这里采用动态webAPi+IResultFilter 替代大部分情况 //20210910 避免冗余的控制器层代码编写,仅仅包了一层前后台定义的格式 这里采用动态webAPi+IResultFilter 替代大部分情况
public static void AddDynamicWebApiSetup(this IServiceCollection services) public static void AddDynamicWebApiSetup(this IServiceCollection services)
@ -25,38 +20,5 @@ namespace IRaCIS.Core.API
}); });
} }
public static void AddMasaMinimalAPiSetUp(this IServiceCollection services)
{
services.AddMasaMinimalAPIs(options =>
{
options.Prefix = "";//自定义前缀 默认是api
options.Version = ""; //默认是V1
options.AutoAppendId = false; //路由是否自动附加参数Id 默认是true
options.PluralizeServiceName = false; //服务名称是否启用复数
//options.Assemblies = new List<Assembly>() { typeof(UserSiteSurveySubmitedEventConsumer).Assembly };
options.GetPrefixes = new List<string> { "Get", "Select", "Find" };
options.PostPrefixes = new List<string> { "Post", "Add", "Create", "List" };
options.PutPrefixes = new List<string> { "Put", "Update" };
options.DeletePrefixes = new List<string> { "Delete", "Remove" };
options.RouteHandlerBuilder = t => {
t.RequireAuthorization()
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
.AddEndpointFilter<TrialGlobalLimitEndpointFilter>()
//.AddEndpointFilter<ModelValidationEndpointFilter>()
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
.WithGroupName("Institution").DisableAntiforgery();
};
options.MapHttpMethodsForUnmatched = new string[] { "Post" };
options.DisableTrimMethodPrefix = true; //禁用去除方法前缀
options.DisableAutoMapRoute = false;//可通过配置true禁用全局自动路由映射或者删除此配置以启用全局自动路由映射
});
}
} }
} }

View File

@ -1,6 +1,6 @@
using EntityFramework.Exceptions.SqlServer; using EntityFramework.Exceptions.SqlServer;
using Hangfire.SqlServer;
using IRaCIS.Core.Application.Triggers; using IRaCIS.Core.Application.Triggers;
using IRaCIS.Core.Application.Triggers.AfterSaveTrigger;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infra.EFCore.Interceptor; using IRaCIS.Core.Infra.EFCore.Interceptor;
@ -12,12 +12,14 @@ using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
public static class EFSetup public static class EFSetup
{ {
public static void AddEFSetup(this IServiceCollection services, IConfiguration configuration, string envName) public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration,string envName)
{ {
services.AddHttpContextAccessor(); services.AddHttpContextAccessor();
@ -35,9 +37,9 @@ namespace IRaCIS.Core.API
// 在控制台 // 在控制台
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); }); //public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); }); var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value; var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value ;
if (!string.IsNullOrWhiteSpace(dbType) && dbType == "pgsql") if (!string.IsNullOrWhiteSpace(dbType) && dbType == "pgsql")
{ {
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure()); options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
@ -52,6 +54,7 @@ namespace IRaCIS.Core.API
//迁移的时候,不生成外键 //迁移的时候,不生成外键
options.ReplaceService<IMigrationsSqlGenerator, NoForeignKeyMigrationsSqlGenerator>(); options.ReplaceService<IMigrationsSqlGenerator, NoForeignKeyMigrationsSqlGenerator>();
options.UseLoggerFactory(logFactory); options.UseLoggerFactory(logFactory);
options.UseExceptionProcessor(); options.UseExceptionProcessor();
@ -63,13 +66,17 @@ namespace IRaCIS.Core.API
options.UseProjectables(); options.UseProjectables();
//options.AddInterceptors(new AuditingInterceptor(configuration.GetSection("ConnectionStrings:RemoteNew").Value));
//options.UseTriggers(triggerOptions => triggerOptions.AddTrigger<SubjectVisitImageDateTrigger>());
//options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers(typeof(SubjectVisitTrigger).Assembly)); //options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers(typeof(SubjectVisitTrigger).Assembly));
options.UseTriggers(triggerOptions => options.UseTriggers(triggerOptions =>
{ {
triggerOptions.AddTrigger<SubjectTrigger>(); triggerOptions.AddTrigger<SubjectTrigger>();
triggerOptions.AddTrigger<ChallengeStateTrigger>(); triggerOptions.AddTrigger<ChallengeStateTrigger>();
triggerOptions.AddTrigger<SubjectVisitTrigger>(); triggerOptions.AddTrigger<SubjectVisitTrigger>();
triggerOptions.AddTrigger<SubjectVisitScanDateTrigger>(); triggerOptions.AddTrigger<SubjectVisitScanDateTrigger>();
triggerOptions.AddTrigger<TrialCriterionSignTrigger>(); triggerOptions.AddTrigger<TrialCriterionSignTrigger>();
@ -80,15 +87,13 @@ namespace IRaCIS.Core.API
triggerOptions.AddTrigger<UserLogTrigger>(); triggerOptions.AddTrigger<UserLogTrigger>();
triggerOptions.AddTrigger<UserAddTrigger>();
triggerOptions.AddTrigger<UserLogAfterTrigger>();
triggerOptions.AddTrigger<IdenttiyUserRoleInfoTrigger>();
}); });
}); });
// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above, // Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,

View File

@ -0,0 +1,24 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.API
{
public static class EasyCachingSetup
{
public static void AddEasyCachingSetup(this IServiceCollection services, IConfiguration configuration)
{
//services.AddEasyCaching(options =>
//{
// options.UseInMemory();
// //options.UseRedis(configuration, EasyCachingConstValue.DefaultRedisName).WithMessagePack(EasyCachingConstValue.DefaultRedisName);
//});
////services.ConfigureCastleInterceptor(options => options.CacheProviderName = EasyCachingConstValue.DefaultRedisName);
//services.ConfigureCastleInterceptor(options => options.CacheProviderName = EasyCachingConstValue.DefaultInMemoryName);
}
}
}

View File

@ -1,6 +1,8 @@
using AspNetCoreRateLimit; using AspNetCoreRateLimit;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
@ -11,7 +13,7 @@ namespace IRaCIS.Core.API
{ {
public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration) public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration)
{ {
// needed to store rate limit counters and ip rules // needed to store rate limit counters and ip rules
services.AddMemoryCache(); services.AddMemoryCache();

View File

@ -0,0 +1,17 @@
using IRaCIS.Core.Domain.Share;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.API
{
public static class JsonConfigSetup
{
public static void AddJsonConfigSetup(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<ServiceVerifyConfigOption>(configuration.GetSection("BasicSystemConfig"));
}
}
}

View File

@ -1,25 +1,26 @@
 
//using LogDashboard; using LogDashboard;
//using Microsoft.Extensions.DependencyInjection; using LogDashboard.Authorization.Filters;
using Microsoft.Extensions.DependencyInjection;
//namespace IRaCIS.Core.API namespace IRaCIS.Core.API
//{ {
// public static class LogDashboardSetup public static class LogDashboardSetup
// { {
// public static void AddLogDashboardSetup(this IServiceCollection services) public static void AddLogDashboardSetup(this IServiceCollection services)
// { {
// //IIS 配置虚拟路径部署会出现IIS静态文件404 //IIS 配置虚拟路径部署会出现IIS静态文件404
// services.AddLogDashboard(opt => services.AddLogDashboard(opt =>
// { {
// //opt.PathMatch = "/api/LogDashboard"; //opt.PathMatch = "/api/LogDashboard";
// opt.PathMatch = "/LogDashboard"; opt.PathMatch = "/LogDashboard";
// //opt.AddAuthorizationFilter(new LogDashboardBasicAuthFilter("admin", "zhizhun2018")); //opt.AddAuthorizationFilter(new LogDashboardBasicAuthFilter("admin", "zhizhun2018"));
// //opt.AddAuthorizationFilter(new LogDashBoardAuthFilter()); //opt.AddAuthorizationFilter(new LogDashBoardAuthFilter());
// }); });
// } }
// } }
//} }

View File

@ -1,120 +0,0 @@
using IRaCIS.Core.API.HostService;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace IRaCIS.Core.API
{
public static class MassTransitSetup
{
public static void AddMassTransitSetup(this IServiceCollection services)
{
#region MassTransit
//masstransit组件 也支持MediatR 中介者模式但是支持分布式考虑后续所以在次替代MediatR
//参考链接https://masstransit.io/documentation/concepts/mediator#scoped-mediator
services.AddMediator(cfg =>
{
cfg.AddConsumers(typeof(UserSiteSurveySubmitedEventConsumer).Assembly);
//cfg.AddConsumer<ConsistencyCheckConsumer>();
//cfg.AddConsumer<AddSubjectTriggerConsumer>();
//cfg.AddConsumer<AddSubjectTriggerConsumer2>();
//cfg.ConfigureMediator((context, cfg) => cfg.UseHttpContextScopeFilter(context));
});
//添加 MassTransit 和 InMemory 传输
services.AddMassTransit(cfg =>
{
cfg.AddConsumers(typeof(UserSiteSurveySubmitedEventConsumer).Assembly);
cfg.AddPublishMessageScheduler();
cfg.AddHangfireConsumers();
// 使用 InMemory 作为消息传递机制
cfg.UsingInMemory((context, cfg) =>
{
cfg.UsePublishMessageScheduler();
cfg.UseConsumeFilter(typeof(ConsumeExceptionFilter<>), context,
x => x.Include(type => type.IsAssignableTo(typeof(DomainEvent))));
cfg.UseConsumeFilter(typeof(CultureInfoFilter<>), context,
x => x.Include(type => type.IsAssignableTo(typeof(DomainEvent))));
cfg.ConfigureEndpoints(context); // 自动配置所有消费者的端点
});
#region rabitmq obsolute
//cfg.UsingRabbitMq((context, cfg) =>
//{
// cfg.UsePublishMessageScheduler();
// cfg.Host(
// host: "106.14.89.110",
// port: 5672,
// virtualHost: "/",
// configure: hostConfig =>
// {
// hostConfig.Username("rabbitmq");
// hostConfig.Password("rabbitmq");
// });
// cfg.ConfigureEndpoints(context);
//});
#endregion
#region Outbox obsolute
//cfg.AddConfigureEndpointsCallback((context, name, cfg) =>
//{
// cfg.UseEntityFrameworkOutbox<IRaCISDBContext>(context);
// //cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30)));
// //// 全局重试策略:重试 3 次,每次延迟 5 秒
// //cfg.UseMessageRetry(retryConfig =>
// //{
// // retryConfig.Interval(3, TimeSpan.FromSeconds(10));
// //});
//});
//cfg.AddEntityFrameworkOutbox<IRaCISDBContext>(o =>
//{
// o.UseSqlServer();
// o.UseBusOutbox();
//});
#endregion
});
//services.AddOptions<MassTransitHostOptions>()
// .Configure(options =>
// {
// options.WaitUntilStarted = true;
// options.StartTimeout = TimeSpan.FromMinutes(1);
// options.StopTimeout = TimeSpan.FromMinutes(1);
// });
//services.AddOptions<HostOptions>()
// .Configure(options => options.ShutdownTimeout = TimeSpan.FromMinutes(1));
//services.AddHostedService<RecurringJobConfigurationService>();
#endregion
}
}
}

View File

@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using StackExchange.Redis;
using System; using System;
using System.Globalization; using System.Globalization;
@ -13,9 +14,9 @@ namespace IRaCIS.Core.API
public class JSONTimeZoneConverter(IHttpContextAccessor _httpContextAccessor) : DateTimeConverterBase public class JSONTimeZoneConverter(IHttpContextAccessor _httpContextAccessor) : DateTimeConverterBase
{ {
private TimeZoneInfo _clientTimeZone; private TimeZoneInfo _clientTimeZone;
private string _dateFormat; private string _dateFormat;
@ -58,7 +59,7 @@ namespace IRaCIS.Core.API
// 仅支持 DateTime 类型的转换 // 仅支持 DateTime 类型的转换
return objectType == typeof(DateTime) || objectType == typeof(DateTime?); return objectType == typeof(DateTime)|| objectType == typeof(DateTime?);
} }
@ -92,20 +93,20 @@ namespace IRaCIS.Core.API
return null; return null;
} }
} }
// 将客户端时间转换为服务器时区的时间 // 将客户端时间转换为服务器时区的时间
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local); var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
return serverZoneTime; return serverZoneTime;
} }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
DateTime? nullableDateTime = value as DateTime?; DateTime? nullableDateTime = value as DateTime?;
if (nullableDateTime != null && nullableDateTime.HasValue) if (nullableDateTime != null && nullableDateTime.HasValue)
@ -120,79 +121,8 @@ namespace IRaCIS.Core.API
else else
{ {
writer.WriteNull(); writer.WriteNull();
} }
} }
} }
#region 废弃
public class MyDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return reader.ReadAsDateTime().Value;
}
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
string dateFormat;
if (!isEn_US)
{
// Chinese date format
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
else
{
// Default or English date format
dateFormat = "MM/dd/yyyy HH:mm:ss";
}
writer.WriteValue(value.ToString(dateFormat));
}
}
public class MyNullableDateTimeConverter : JsonConverter<DateTime?>
{
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var val = reader.ReadAsDateTime();
return val;
}
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
string dateFormat;
if (!isEn_US)
{
// Chinese date format
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
else
{
// Default or English date format
dateFormat = "MM/dd/yyyy HH:mm:ss";
}
if (value.HasValue)
{
writer.WriteValue(value.Value.ToString(dateFormat));
}
else
{
writer.WriteValue(default(DateTime?));
}
}
}
#endregion
} }

View File

@ -0,0 +1,99 @@
using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Globalization;
using System.Reflection;
namespace IRaCIS.Core.API
{
//public class CustomContractResolver : DefaultContractResolver
//{
// protected override JsonContract CreateContract(Type objectType)
// {
// var contract = base.CreateContract(objectType);
// // 检查类是否有 LowercaseJsonAttribute 标记
// if (objectType.GetCustomAttribute<LowerCamelCaseJsonAttribute>() != null)
// {
// contract.NamingStrategy = new IRCCamelCaseNamingStrategy();
// }
// return contract;
// }
//}
#region 废弃
public class MyDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return reader.ReadAsDateTime().Value;
}
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
string dateFormat;
if (!isEn_US)
{
// Chinese date format
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
else
{
// Default or English date format
dateFormat = "MM/dd/yyyy HH:mm:ss";
}
writer.WriteValue(value.ToString(dateFormat));
}
}
public class MyNullableDateTimeConverter : JsonConverter<DateTime?>
{
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var val = reader.ReadAsDateTime();
return val;
}
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
string dateFormat;
if (!isEn_US)
{
// Chinese date format
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
else
{
// Default or English date format
dateFormat = "MM/dd/yyyy HH:mm:ss";
}
if (value.HasValue)
{
writer.WriteValue(value.Value.ToString(dateFormat));
}
else
{
writer.WriteValue(default(DateTime?));
}
}
}
#endregion
}

View File

@ -1,9 +1,16 @@
 
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson; using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System; using System;
using System.Globalization;
using System.Text.Json;
using Newtonsoft.Json.Serialization;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
@ -22,6 +29,13 @@ namespace IRaCIS.Core.API
builder.AddNewtonsoftJson(options => builder.AddNewtonsoftJson(options =>
{ {
//大驼峰
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//小驼峰
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
// 忽略循环引用 // 忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
@ -33,18 +47,10 @@ namespace IRaCIS.Core.API
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
#region 废弃
//大驼峰
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//小驼峰
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
//二者只能取其一 //二者只能取其一
//options.SerializerSettings.Converters.Add(new MyDateTimeConverter()); //options.SerializerSettings.Converters.Add(new MyDateTimeConverter());
//options.SerializerSettings.Converters.Add(new MyNullableDateTimeConverter()); //options.SerializerSettings.Converters.Add(new MyNullableDateTimeConverter());
#endregion
//必须放在后面 //必须放在后面
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>()); options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
@ -56,7 +62,6 @@ namespace IRaCIS.Core.API
{ {
o.SuppressModelStateInvalidFilter = true; //自己写验证 o.SuppressModelStateInvalidFilter = true; //自己写验证
#region 废弃验证
////这里是自定义验证结果和返回状态码 因为这里是在[ApiController]控制器层校验动态webApi的不会校验 所以需要单独写一个Filter ////这里是自定义验证结果和返回状态码 因为这里是在[ApiController]控制器层校验动态webApi的不会校验 所以需要单独写一个Filter
//o.InvalidModelStateResponseFactory = (context) => //o.InvalidModelStateResponseFactory = (context) =>
//{ //{
@ -67,7 +72,7 @@ namespace IRaCIS.Core.API
//return new JsonResult(ResponseOutput.NotOk("The inputs supplied to the API are invalid. " + JsonConvert.SerializeObject( error))); //return new JsonResult(ResponseOutput.NotOk("The inputs supplied to the API are invalid. " + JsonConvert.SerializeObject( error)));
//}; //};
#endregion
}); });

View File

@ -8,18 +8,34 @@ using System.Reflection;
namespace IRaCIS.Core.API namespace IRaCIS.Core.API
{ {
/// <summary>
/// LowerCamelCaseJsonAttribute 可以设置类小写返回给前端
/// </summary>
public class NullToEmptyStringResolver : DefaultContractResolver public class NullToEmptyStringResolver : DefaultContractResolver
{ {
/// <summary>
/// 创建属性
/// </summary>
/// <param name="type">类型</param>
/// <param name="memberSerialization">序列化成员</param>
/// <returns></returns>
//protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
//{
// IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
// foreach (var jsonProperty in properties)
// {
// jsonProperty.DefaultValue = new NullToEmptyStringValueProvider(jsonProperty);
// }
// return properties;
//}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{ {
// 检查类是否有 LowerCamelCaseJsonAttribute 标记 有的话,属性名小写 // 检查类是否有 LowerCamelCaseJsonAttribute 标记 有的话,属性名小写
if (type.GetCustomAttribute<LowerCamelCaseJsonAttribute>() != null) if (type.GetCustomAttribute<LowerCamelCaseJsonAttribute>() != null)
{ {
base.NamingStrategy = new LowerCamelCaseNamingStrategy(); base.NamingStrategy= new LowerCamelCaseNamingStrategy();
} }
else else
{ {
@ -30,7 +46,7 @@ namespace IRaCIS.Core.API
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization); IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
var list = type.GetProperties() var list= type.GetProperties()
.Select(p => .Select(p =>
{ {
var jp = base.CreateProperty(p, memberSerialization); var jp = base.CreateProperty(p, memberSerialization);
@ -41,45 +57,12 @@ namespace IRaCIS.Core.API
var uu = list.Select(t => t.PropertyName).ToList(); var uu = list.Select(t => t.PropertyName).ToList();
//获取复杂对象属性 //获取复杂对象属性
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList(); properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
list.AddRange(properties); list.AddRange(properties);
return list; return list;
} }
} }
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

@ -2,7 +2,7 @@
using System.Reflection; using System.Reflection;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
namespace IRaCIS.Core.SCP namespace IRaCIS.Core.API
{ {
public class NullToEmptyStringValueProvider : IValueProvider public class NullToEmptyStringValueProvider : IValueProvider
@ -28,7 +28,7 @@ namespace IRaCIS.Core.SCP
if(_MemberInfo.PropertyType == typeof(string)) if(_MemberInfo.PropertyType == typeof(string))
{ {
//去掉前后空格 //去掉前后空格
_MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value.ToString().Trim()); _MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value/*.ToString().Trim()*/);
} }
else else

View File

@ -1,5 +1,8 @@
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System; using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -11,7 +14,7 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
private readonly IOSSService _oSSService; private readonly IOSSService _oSSService;
// 构造函数 // 构造函数
public ObjectStorePathConvert(IOSSService oSSService) public ObjectStorePathConvert( IOSSService oSSService)
{ {
_oSSService = oSSService; _oSSService = oSSService;
} }
@ -38,9 +41,9 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
Regex guidRegex = new Regex(@"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"); Regex guidRegex = new Regex(@"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
if (propertyName.IndexOf("Path") > -1 && value.IndexOf('.') > -1 && guidRegex.IsMatch(value)) if (propertyName.IndexOf("Path") > -1 && value.IndexOf('.')>-1 && guidRegex.IsMatch(value))
{ {
var tt = _oSSService.GetSignedUrl(value); var tt= _oSSService.GetSignedUrl(value);
writer.WriteValue(tt); writer.WriteValue(tt);
} }
else else
@ -48,13 +51,13 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
// 将处理后的字符串写入到 JsonWriter 中 // 将处理后的字符串写入到 JsonWriter 中
writer.WriteValue(value); writer.WriteValue(value);
} }
} }
else else
{ {
writer.WriteNull(); writer.WriteNull();
} }
} }
} }
} }

View File

@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Http;
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using System;
namespace IRaCIS.Core.API
{
public static class EnricherExtensions
{
public static LoggerConfiguration WithHttpContextInfo(this LoggerEnrichmentConfiguration enrich, IServiceProvider serviceProvider)
{
if (enrich == null)
throw new ArgumentNullException(nameof(enrich));
return enrich.With(new HttpContextEnricher(serviceProvider));
}
public static LoggerConfiguration WithHttpContextInfo(this LoggerEnrichmentConfiguration enrich, IServiceProvider serviceProvider, Action<LogEvent, ILogEventPropertyFactory, HttpContext> enrichAction)
{
if (enrich == null)
throw new ArgumentNullException(nameof(enrich));
return enrich.With(new HttpContextEnricher(serviceProvider, enrichAction));
}
}
}

View File

@ -0,0 +1,89 @@
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Core;
using Serilog.Events;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.API
{
public class HttpContextEnricher : ILogEventEnricher
{
private readonly IServiceProvider _serviceProvider;
private readonly Action<LogEvent, ILogEventPropertyFactory, HttpContext> _enrichAction;
public HttpContextEnricher(IServiceProvider serviceProvider) : this(serviceProvider, null)
{ }
public HttpContextEnricher(IServiceProvider serviceProvider, Action<LogEvent, ILogEventPropertyFactory, HttpContext> enrichAction)
{
_serviceProvider = serviceProvider;
if (enrichAction == null)
{
_enrichAction = (logEvent, propertyFactory, httpContext) =>
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestIP", httpContext.Connection.RemoteIpAddress.ToString()));
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("LocalIP", httpContext.Connection.LocalIpAddress.MapToIPv4().ToString()));
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TokenUserRealName", httpContext?.User?.FindFirst(ClaimAttributes.RealName)?.Value));
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TokenUserType", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value));
//这样读取没用
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestBody", await ReadRequestBody(httpContext.Request)));
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestIP", IPHelper.GetIP(httpContext.Request) ));
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Referer", httpContext.Request.Headers["Referer"].ToString()));
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("request_path", httpContext.Request.Path));
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("request_method", httpContext.Request.Method));
//if (httpContext.Response.HasStarted)
//{
// logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("response_status", httpContext.Response.StatusCode));
//}
};
}
else
{
_enrichAction = enrichAction;
}
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var httpContext = _serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext;
if (null != httpContext)
{
_enrichAction.Invoke(logEvent, propertyFactory, httpContext);
}
}
private async Task<string> ReadRequestBody(HttpRequest request)
{
// Ensure the request's body can be read multiple times (for the next middlewares in the pipeline).
request.EnableBuffering();
using var streamReader = new StreamReader(request.Body, leaveOpen: true);
var requestBody = await streamReader.ReadToEndAsync();
// Reset the request's body stream position for next middleware in the pipeline.
request.Body.Position = 0;
return requestBody==null?String.Empty: requestBody.Trim();
}
private async Task<string> ReadResponseBody(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string responseBody = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"{responseBody}";
}
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Builder;
using Serilog;
using Serilog.Events;
//using Serilog.Sinks.Email;
using System;
using System.Net;
namespace IRaCIS.Core.API
{
public class SerilogExtension
{
public static void AddSerilogSetup(string environment, IServiceProvider serviceProvider)
{
var config = 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()
.Filter.ByExcluding(logEvent =>logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
//控制台 方便调试 问题 我们显示记录日志 时 获取上下文的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}");
//.WriteTo.MSSqlServer("Data Source=DESKTOP-4TU9A6M;Initial Catalog=CoreFrame;User ID=sa;Password=123456", "logs", autoCreateSqlTable: true, restrictedToMinimumLevel: LogEventLevel.Information)//从左至右四个参数分别是数据库连接字符串、表名、如果表不存在是否创建、最低等级。Serilog会默认创建一些列。
//if (environment == "Production")
//{
// config.WriteTo.Email(new EmailConnectionInfo()
// {
// EmailSubject = "系统警告,请速速查看!",//邮件标题
// FromEmail = "test@extimaging.com",//发件人邮箱
// MailServer = "smtp.qiye.aliyun.com",//smtp服务器地址
// NetworkCredentials = new NetworkCredential("test@extimaging.com", "SHzyyl2021"),//两个参数分别是发件人邮箱与客户端授权码
// Port = 465,//端口号
// ToEmail = "872297557@qq.com"//收件人
// }, restrictedToMinimumLevel: LogEventLevel.Error,
// outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [ {Level} {ClientIp} {ClientAgent} {TokenUserRealName} {TokenUserType} ] || [path: {RequestPath} arguments: {RequestBody}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine})");
//}
//扩展方法 获取上下文的ip 用户名 用户类型
Log.Logger = config.Enrich.WithHttpContextInfo(serviceProvider).CreateLogger();
}
}
}

View File

@ -1,76 +0,0 @@
using Amazon.SecurityToken.Model;
using DocumentFormat.OpenXml.Bibliography;
using IRaCIS.Core.Domain.Share;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;
using Serilog.Formatting.Display;
using System;
using System.Collections.Generic;
using System.Net;
namespace IRaCIS.Core.API
{
public class SerilogExtension
{
public static void AddSerilogSetup(string environment)
{
var config = 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("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:60);
#region 根据环境配置是否打开错误发送邮件通知
//读取配置文件
var configuration = new ConfigurationBuilder().Add(new JsonConfigurationSource { Path = $"appsettings.{environment}.json", ReloadOnChange = true }).Build();
// 手动绑定配置
var emailConfig = new SystemEmailSendConfig();
configuration.GetSection("SystemEmailSendConfig").Bind(emailConfig);
if (emailConfig.IsOpenErrorNoticeEmail)
{
config.WriteTo.Email(options: new Serilog.Sinks.Email.EmailSinkOptions()
{
From = emailConfig.FromEmail,
To = emailConfig.ErrorNoticeEmailList,
Host = emailConfig.Host,
Port = emailConfig.Port,
Subject = new MessageTemplateTextFormatter("Log Alert - 系统发生了异常,请核查"),
Credentials = new NetworkCredential(emailConfig.FromEmail, emailConfig.AuthorizationCode)
}, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error);
}
#endregion
Log.Logger = config.CreateLogger();
#region 废弃-输出Json格式的日志
//如果有反向代理并不会获取到用户的真实IP
//.Enrich.WithClientIp()
//.Enrich.WithRequestHeader("User-Agent")
//https://github.com/serilog/serilog-formatting-compact
//// 控制台输出 JSON 格式
//.WriteTo.Console(formatter: new CompactJsonFormatter(), LogEventLevel.Warning),
//// 文件输出 JSON 格式
//.WriteTo.File(new CompactJsonFormatter(), $"{AppContext.BaseDirectory}Serilogs/.json", rollingInterval: RollingInterval.Day);
#endregion
}
}
}

View File

@ -1,136 +0,0 @@
using IP2Region.Net.Abstractions;
using IP2Region.Net.XDB;
using IRaCIS.Core.Application.BackGroundJob;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Panda.DynamicWebApi;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace IRaCIS.Core.API;
public static class ServiceCollectionSetup
{
public static void ConfigureServices(this IServiceCollection services, IConfiguration _configuration)
{
services.AddOptions().Configure<SystemEmailSendConfig>(_configuration.GetSection("SystemEmailSendConfig"));
services.AddOptions().Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
//转发头设置 获取真实IP
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
services.AddSingleton<IUserIdProvider, IRaCISUserIdProvider>();
services.AddSingleton<ISearcher>(new Searcher(CachePolicy.Content, Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources, "ip2region.xdb")));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped<IRepository, Repository>();
services.AddScoped<IObtainTaskAutoCancelJob, ObtainTaskAutoCancelJob>();
// 注册以Service 结尾的服务
services.Scan(scan => scan
.FromAssemblies(typeof(BaseService).Assembly)
.AddClasses(classes => classes.Where(t => t.Name.Contains("Service")))
.AsImplementedInterfaces()
.WithScopedLifetime());
#region Register Controllers
// Register all controllers in the assembly that implement IDynamicWebApi
services.Scan(scan => scan
.FromAssemblies(typeof(BaseService).Assembly)
.AddClasses(classes => classes.AssignableTo<IDynamicWebApi>())
.AsSelf()
.WithScopedLifetime());
#endregion
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
#region 历史废弃配置
//builder.Services.AddMemoryCache();
////上传限制 配置
//builder.Services.Configure<FormOptions>(options =>
//{
// options.MultipartBodyLengthLimit = int.MaxValue;
// options.ValueCountLimit = int.MaxValue;
// options.ValueLengthLimit = int.MaxValue;
//});
//IP 限流 可设置白名单 或者黑名单
//services.AddIpPolicyRateLimitSetup(_configuration);
// 用户类型 策略授权
//services.AddAuthorizationPolicySetup(_configuration);
#endregion
}
}
#region Autofac 废弃
//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.Application.dll");
// containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
// .PropertiesAutowired().AsImplementedInterfaces();
// //containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
// //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
// //注册hangfire任务 依赖注入
// containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
// }
//}
#endregion

View File

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace IRaCIS.Core.API
{
public static class StaticFileAuthorizationSetup
{
public static void AddStaticFileAuthorizationSetup(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
}
}

View File

@ -0,0 +1,137 @@
using IRaCIS.Core.Application.Contracts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace IRaCIS.Core.API
{
public static class SwaggerSetup
{
public static void AddSwaggerSetup(this IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
//此处的Name 是控制器上分组的名称 Title是界面的大标题
//分组
options.SwaggerDoc("Reviewer", new OpenApiInfo {Title = "医生模块",Version = "Reviewer", });
options.SwaggerDoc("Trial", new OpenApiInfo { Title = "项目模块", Version = "Trial" });
options.SwaggerDoc("Enroll", new OpenApiInfo { Title = "入组模块", Version = "Enroll" });
options.SwaggerDoc("Workload", new OpenApiInfo { Title = "工作量模块", Version = "Workload" });
options.SwaggerDoc("Common", new OpenApiInfo { Title = "通用信息获取", Version = "Common" });
options.SwaggerDoc("Institution", new OpenApiInfo { Title = "机构信息模块", Version = "Institution" });
options.SwaggerDoc("Dashboard&Statistics", new OpenApiInfo { Title = "统计模块", Version = "Dashboard&Statistics" });
options.SwaggerDoc("Financial", new OpenApiInfo { Title = "财务模块", Version = "Financial" });
options.SwaggerDoc("Management", new OpenApiInfo { Title = "管理模块", Version = "Management" });
options.SwaggerDoc("Image", new OpenApiInfo { Title = "影像模块", Version = "Image" });
options.SwaggerDoc("Reading", new OpenApiInfo { Title = "读片模块", Version = "Reading" });
// 接口排序
options.OrderActionsBy(o => o.GroupName);
options.DocInclusionPredicate((docName, apiDes) =>
{
if (!apiDes.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
var versions = methodInfo.DeclaringType.GetCustomAttributes(true)
.OfType<ApiExplorerSettingsAttribute>()
.Select(attr => attr.GroupName);
return versions.Any(v => v.ToString() == docName);
});
var xmlPath = Path.Combine(AppContext.BaseDirectory, "IRaCIS.Core.API.xml");//这个就是刚刚配置的xml文件名
options.IncludeXmlComments(xmlPath, true);
var xmlPath2 = Path.Combine(AppContext.BaseDirectory, "IRaCIS.Core.Application.xml");//这个就是刚刚配置的xml文件名
options.IncludeXmlComments(xmlPath2, true);
//默认的第二个参数是false这个是controller的注释记得修改
// 在header中添加token传递到后台
options.OperationFilter<SecurityRequirementsOperationFilter>();
// 添加登录按钮
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
//In = "header",
//Type = "apiKey"
});
//// Bearer
//options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
//{
// Description = "JWT Authorization header using the Bearer scheme.",
// Name = "Authorization",
// In = ParameterLocation.Header,
// Scheme = "bearer",
// Type = SecuritySchemeType.Http,
// BearerFormat = "JWT"
//});
});
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
//此处的Name 是页面 选择文档下拉框 显示的名称
options.SwaggerEndpoint($"swagger/Reviewer/swagger.json", "医生模块");
options.SwaggerEndpoint($"swagger/Trial/swagger.json", "项目模块");
options.SwaggerEndpoint($"swagger/Enroll/swagger.json", "入组模块");
options.SwaggerEndpoint($"swagger/Workload/swagger.json", "工作量模块");
options.SwaggerEndpoint($"swagger/Dashboard&Statistics/swagger.json", "统计模块");
options.SwaggerEndpoint($"swagger/Common/swagger.json", "通用模块");
options.SwaggerEndpoint($"swagger/Financial/swagger.json", "财务模块");
options.SwaggerEndpoint($"swagger/Institution/swagger.json", "机构信息模块");
options.SwaggerEndpoint($"swagger/Management/swagger.json", "管理模块");
options.SwaggerEndpoint($"swagger/Image/swagger.json", "影像模块");
options.SwaggerEndpoint($"swagger/Reading/swagger.json", "读片模块");
//路径配置设置为空表示直接在根域名localhost:8001访问该文件,
//注意localhost:8001/swagger是访问不到的去launchSettings.json把launchUrl去掉如果你想换一个路径直接写名字即可比如直接写c.Route = "doc";
//options.RoutePrefix = string.Empty;
var data = Assembly.GetExecutingAssembly().Location;
options.IndexStream = () => Assembly.GetExecutingAssembly()
.GetManifestResourceStream("IRaCIS.Core.API.wwwroot.swagger.ui.Index.html");
options.RoutePrefix = string.Empty;
//DocExpansion设置为none可折叠所有方法
options.DocExpansion(DocExpansion.None);
//DefaultModelsExpandDepth设置为 - 1 可不显示models
options.DefaultModelsExpandDepth(-1);
// 引入静态文件添加登录功能
// 清除静态文件缓存
// options.IndexStream = () => null;
});
}
}
}

View File

@ -1,130 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
namespace IRaCIS.Core.API;
public enum SwaggerVersion
{
[Description("文件记录(FileRecord)")]
FileRecord = -1,
[Description("医生模块(Reviewer)")]
Reviewer = 1,
[Description("项目模块(Trial)")]
Trial = 2,
[Description("入组模块(Enroll)")]
Enroll = 3,
[Description("工作量模块(Workload)")]
Workload = 4,
[Description("通用信息获取(Common)")]
Common = 5,
[Description("机构信息模块(Institution)")]
Institution = 6,
[Description("统计模块(DashboardStatistics)")]
DashboardStatistics = 7,
[Description("财务模块(Financial)")]
Financial = 8,
[Description("管理模块(Management)")]
Management =9,
[Description("影像模块(Image)")]
Image =10,
[Description("读片模块(Reading)")]
Reading =11
};
public static class SwaggerSetup
{
public static void AddSwaggerSetup(this IServiceCollection services)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
typeof(SwaggerVersion).GetFields(BindingFlags.Public | BindingFlags.Static).ToList()
.ForEach(field =>
{
var description = field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? field.Name;
options.SwaggerDoc(field.Name, new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = field.Name,
Description = $"{field.Name} API",
Title = description // 使用Description作为Title
});
});
// 接口排序
options.OrderActionsBy(o => o.GroupName);
//添加注释
var basePath = AppContext.BaseDirectory;
var xmlPath1 = Path.Combine(basePath, "IRaCIS.Core.Application.xml");
var xmlPath2 = Path.Combine(basePath, "IRaCIS.Core.API.xml");
options.IncludeXmlComments(xmlPath1, true);
options.IncludeXmlComments(xmlPath2, true);
// 在header中添加token传递到后台
options.OperationFilter<SecurityRequirementsOperationFilter>();
// 添加登录按钮
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
//In = "header",
//Type = "apiKey"
});
});
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
typeof(SwaggerVersion).GetFields(BindingFlags.Public | BindingFlags.Static).ToList()
.ForEach(field =>
{
var description = field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? field.Name;
options.SwaggerEndpoint($"swagger/{field.Name}/swagger.json", $"{description}");
});
var data = Assembly.GetExecutingAssembly().Location;
options.IndexStream = () => Assembly.GetExecutingAssembly()
.GetManifestResourceStream("IRaCIS.Core.API.wwwroot.swagger.ui.Index.html");
//路径配置设置为空表示直接在根域名localhost:8001访问该文件,
//注意localhost:8001/swagger是访问不到的去launchSettings.json把launchUrl去掉如果你想换一个路径直接写名字即可比如直接写c.Route = "doc";
options.RoutePrefix = string.Empty;
//DocExpansion设置为none可折叠所有方法
options.DocExpansion(DocExpansion.None);
//DefaultModelsExpandDepth设置为 - 1 可不显示models
options.DefaultModelsExpandDepth(-1);
});
}
}

View File

@ -27,10 +27,18 @@ namespace IRaCIS.Core.API
hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions() hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions()
{ {
SchemaName = "dbo", SchemaName = "dbo",
}).UseRecommendedSerializerSettings().UseSimpleAssemblyNameTypeSerializer(); CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
});
} }
//hangFireConfig.UseTagsWithSql(); //nuget引入Hangfire.Tags.SqlServer //hangFireConfig.UseTagsWithSql(); //nuget引入Hangfire.Tags.SqlServer
//.UseHangfireHttpJob(); //.UseHangfireHttpJob();
@ -38,8 +46,6 @@ namespace IRaCIS.Core.API
services.AddHangfireServer(option => services.AddHangfireServer(option =>
{ {
//默认15s检查一次
option.SchedulePollingInterval = TimeSpan.FromSeconds(5);
option.Queues = new[] { "immediately_once", "default", "sys_init", "not_immediately_once" }; option.Queues = new[] { "immediately_once", "default", "sys_init", "not_immediately_once" };
}); });

View File

@ -7,8 +7,8 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true", "RemoteNew": "Server=47.117.164.182,1434;Database=Event_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true" "Hangfire": "Server=47.117.164.182,1434;Database=Event_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
}, },
"ObjectStoreService": { "ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS", "ObjectStoreUse": "AliyunOSS",
@ -52,10 +52,7 @@
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90, "ChangePassWordDays": 90
// 1 Elevate 2 Extensive
"TemplateType": 2
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 465, "Port": 465,

View File

@ -7,10 +7,10 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true", //"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true" //"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
//"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true", "RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true" "Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
}, },
"ObjectStoreService": { "ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS", "ObjectStoreUse": "AliyunOSS",
@ -43,36 +43,33 @@
"OpenLoginLimit": true, "OpenLoginLimit": true,
"LoginMaxFailCount": 5, "LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30, "LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120,
"AutoLoginOutMinutes": 360,
"OpenLoginMFA": false, "OpenLoginMFA": false,
"ContinuousReadingTimeMin": 120, "ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive "ChangePassWordDays": 90
"TemplateType": 2
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 465, "Port": 465,
"Host": "smtp.qiye.aliyun.com", "Host": "smtp.qiye.aliyun.com",
"FromEmail": "irc@extimaging.com", "FromEmail": "IRC@extimaging.com",
"FromName": "IRC Imaging System", "FromName": "IRC",
"AuthorizationCode": "ExtImg@2022", "AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login", "SiteUrl": "http://irc.extimaging.com/login",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging", "OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging", "OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging", "CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司", "CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging", "CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"IsEnv_US": false, "IsEnv_US": false
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {
"Port": "11113", "Port": "11113",

View File

@ -23,11 +23,9 @@
"AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV", "AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
"RoleArn": "acs:ram::1899121822495495:role/dev-oss-access", "RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
"BucketName": "zy-irc-test-store", "BucketName": "zy-irc-test-store",
//"ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com", "ViewEndpoint": "https://zy-irc-test-store.oss-cn-shanghai.aliyuncs.com",
"ViewEndpoint": "https://zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai", "Region": "oss-cn-shanghai",
"DurationSeconds": 7200, "DurationSeconds": 7200
"PreviewEndpoint": "https://test-oss.test.extimaging.com"
}, },
"MinIO": { "MinIO": {
"EndPoint": "hir-oss.test.extimaging.com", "EndPoint": "hir-oss.test.extimaging.com",
@ -54,47 +52,44 @@
"BasicSystemConfig": { "BasicSystemConfig": {
"OpenUserComplexPassword": false, "OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false, "OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false, "OpenLoginLimit": false,
"LoginMaxFailCount": 5, "LoginMaxFailCount": 5,
"LoginFailLockMinutes": 1, "LoginFailLockMinutes": 1,
"AutoLoginOutMinutes": 10,
"AutoLoginOutMinutes": 1,
"OpenLoginMFA": false, "OpenLoginMFA": false,
"ContinuousReadingTimeMin": 120, "ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10, "ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive "ChangePassWordDays": 90
"TemplateType": 2,
"OpenTrialRelationDelete": true,
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 465, "Port": 465,
"Host": "smtp.qiye.aliyun.com", "Host": "smtp.qiye.aliyun.com",
"FromEmail": "test@extimaging.com", "FromEmail": "test@extimaging.com",
"FromName": "Test IRC Imaging System", "FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021", "AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.test.extimaging.com/login", "SiteUrl": "http://irc.test.extimaging.com/login",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging", "OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging", "OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging", "CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司", "CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging", "CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"IsEnv_US": false, "IsEnv_US": false
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {

View File

@ -7,8 +7,8 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=us-mssql-prod,1433;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true", "RemoteNew": "Server=us-prod-mssql-service,1433;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=us-mssql-prod,1433;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true" "Hangfire": "Server=us-prod-mssql-service,1433;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
//"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true", //"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
//"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true", //"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
}, },
@ -39,36 +39,34 @@
}, },
"BasicSystemConfig": { "BasicSystemConfig": {
"OpenUserComplexPassword": true, "OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": true,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5, "LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120,
"OpenLoginMFA": true, "LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 60,
"ContinuousReadingTimeMin": 120, "ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10, "ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenTrialRelationDelete": false
"ChangePassWordDays": 90
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 587, "Port": 587,
"Host": "smtp-mail.outlook.com", "Host": "smtp-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai", "FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System", "FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul", "AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging", "OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging", "OrganizationNameCN": "Elevate Imaging",
"CompanyName": "Elevate Imaging Inc.", "CompanyName": "Elevate Imaging Inc.",
@ -76,9 +74,7 @@
"CompanyShortName": "Elevate Imaging", "CompanyShortName": "Elevate Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.elevateimaging.ai/login", "SiteUrl": "https://lili.elevateimaging.ai/login",
"IsEnv_US": true, "IsEnv_US": true
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {

View File

@ -64,8 +64,7 @@
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90, "ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenLoginMFA": true "OpenLoginMFA": true
}, },
@ -73,10 +72,9 @@
"Port": 587, "Port": 587,
"Host": "smtp-mail.outlook.com", "Host": "smtp-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai", "FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System", "FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul", "AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging", "OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging", "OrganizationNameCN": "Elevate Imaging",
"CompanyName": "Elevate Imaging Inc.", "CompanyName": "Elevate Imaging Inc.",
@ -84,9 +82,7 @@
"CompanyShortName": "Elevate Imaging", "CompanyShortName": "Elevate Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.test.elevateimaging.ai/login", "SiteUrl": "https://lili.test.elevateimaging.ai/login",
"IsEnv_US": true, "IsEnv_US": true
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {

View File

@ -43,49 +43,50 @@
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4", "AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
"SecretAccessKey": "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", "SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
"BucketName": "ei-med-s3-lili-uat-store", "BucketName": "ei-med-s3-lili-uat-store",
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com", "ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com/",
"DurationSeconds": 7200 "DurationSeconds": 7200
} }
}, },
"BasicSystemConfig": { "BasicSystemConfig": {
"OpenUserComplexPassword": true, "OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true, "OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true, "OpenTrialRelationDelete": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120,
"OpenLoginMFA": true, "OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 60,
"ContinuousReadingTimeMin": 120, "ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90, "ChangePassWordDays": 90,
// 1 Elevate 2 Extensive
"TemplateType": 1 "OpenLoginMFA": false
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 587, "Port": 587,
"Host": "smtp-mail.outlook.com", "Host": "smtp-mail.outlook.com",
"FromEmail": "donotreply@elevateimaging.ai", "FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System", "FromName": "LiLi",
"AuthorizationCode": "Q#669869497420ul", "AuthorizationCode": "Q#669869497420ul",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging", "OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging", "OrganizationNameCN": "Elevate Imaging",
"CompanyName": "Elevate Imaging Inc.", "CompanyName": "Elevate Imaging Inc.",
"CompanyNameCN": "上海展影医疗科技有限公司", "CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Elevate Imaging", "CompanyShortName": "Elevate Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"SiteUrl": "https://lili.uat.elevateimaging.ai/login", "SiteUrl": "https://lili.test.elevateimaging.ai/login",
"IsEnv_US": true, "IsEnv_US": true
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {

View File

@ -7,8 +7,8 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true", "RemoteNew": "Server=47.117.164.182,1434;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true" "Hangfire": "Server=47.117.164.182,1434;Database=Uat_IRC.Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
}, },
"ObjectStoreService": { "ObjectStoreService": {
@ -18,11 +18,11 @@
"RegionId": "cn-shanghai", "RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com", "InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com", "EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y", "AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T", "AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access", "RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
"BucketName": "tl-med-irc-uat-store", "BucketName": "zy-irc-uat-store",
"ViewEndpoint": "https://tl-med-irc-uat-store.oss-cn-shanghai.aliyuncs.com", "ViewEndpoint": "https://zy-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai", "Region": "oss-cn-shanghai",
"DurationSeconds": 7200 "DurationSeconds": 7200
}, },
@ -56,48 +56,43 @@
"BasicSystemConfig": { "BasicSystemConfig": {
"OpenUserComplexPassword": true, "OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true, "OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true, "OpenLoginLimit": true,
"LoginMaxFailCount": 5, "LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30, "LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120, "AutoLoginOutMinutes": 60,
"OpenLoginMFA": false, "OpenLoginMFA": false,
"ContinuousReadingTimeMin": 120, "ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true, "IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 1 Elevate 2 Extensive "ChangePassWordDays": 90
"TemplateType": 2
}, },
"SystemEmailSendConfig": { "SystemEmailSendConfig": {
"Port": 465, "Port": 465,
"Host": "smtp.qiye.aliyun.com", "Host": "smtp.qiye.aliyun.com",
"FromEmail": "uat@extimaging.com", "FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System", "FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021", "AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login", "SiteUrl": "http://irc.uat.extimaging.com/login",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging", "OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging", "OrganizationNameCN": "Extlmaging",
"CompanyName": "Extensive Imaging", "CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司", "CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging", "CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗", "CompanyShortNameCN": "展影医疗",
"IsEnv_US": false, "IsEnv_US": false
"IsOpenErrorNoticeEmail": false,
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
}, },
"SystemPacsConfig": { "SystemPacsConfig": {
"Port": "11113", "Port": "11113",
"IP": "101.132.253.119" "IP": "47.117.164.182"
} }
} }

View File

@ -3,7 +3,7 @@
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021", "SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
"Issuer": "Extimaging", "Issuer": "Extimaging",
"Audience": "EICS", "Audience": "EICS",
"TokenExpireMinute": "10080"//7 "TokenExpireDays": "7"
}, },
"IpRateLimiting": { "IpRateLimiting": {
"EnableEndpointRateLimiting": true, "EnableEndpointRateLimiting": true,
@ -66,13 +66,5 @@
"ApiPathList": [ "ApiPathList": [
"/test/get" "/test/get"
] ]
},
"oauth": {
"github": {
"app_id": "github_app_id",
"app_key": "github_app_key",
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
"scope": "repo"
}
} }
} }

View File

@ -268,12 +268,12 @@ var abp = abp || {};
} }
function loginUserInternal(tenantId, callback) { function loginUserInternal(tenantId, callback) {
var usernameOrEmailAddress = document.getElementById('userName').value; var usernameOrEmailAddress = document.getElementById('userName').value;
// if (!usernameOrEmailAddress) { if (!usernameOrEmailAddress) {
// alert('UserName Can Not Be Null'); alert('UserName Can Not Be Null');
// return false; return false;
// } }
var password = document.getElementById('password').value; var password = document.getElementById('password').value;
var pwdMd5 = document.getElementById('pwdMd5').value; var pwdMd5 = document.getElementById('pwdMd5').value;
@ -286,12 +286,12 @@ var abp = abp || {};
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.readyState === XMLHttpRequest.DONE) {
// debugger; debugger;
if (xhr.status === 200) { if (xhr.status === 200) {
// debugger; debugger;
var resultdata = JSON.parse(xhr.responseText); var resultdata = JSON.parse(xhr.responseText);
if (resultdata.ErrorMessage != '') { if (resultdata.ErrorMessage != '') {
alert(resultdata.ErrorMessage); alert(resultdata.ErrorMessage);
@ -303,23 +303,18 @@ var abp = abp || {};
else { else {
var responseJSON = JSON.parse(xhr.responseText); var responseJSON = JSON.parse(xhr.responseText);
var result = responseJSON; var result = responseJSON;
var expireDate = new Date(Date.now() + (60 * 60 * 24 * 1000)); var expireDate = new Date(Date.now() + (60*60*24 * 1000));
abp.auth.setToken(result.Result.JWTStr, expireDate); abp.auth.setToken(result.Result.JWTStr, expireDate);
let selectDom = document.getElementById("roleSelect") callback();
selectDom.options.length = 0;
result.Result.BasicInfo.AccountList.forEach(item => {
selectDom.options.add(new Option(item.UserTypeShortName, item.Id));
})
// callback();
} }
} else { } else {
alert('Login failed !'); alert('Login failed !');
} }
} }
}; };
xhr.open('POST', '/User/getUserLoginRoleList', true); xhr.open('POST', '/user/login', true);
xhr.setRequestHeader('Abp.TenantId', tenantId); xhr.setRequestHeader('Abp.TenantId', tenantId);
xhr.setRequestHeader('Content-type', 'application/json'); xhr.setRequestHeader('Content-type', 'application/json');
var parm = { var parm = {
@ -335,63 +330,7 @@ var abp = abp || {};
//xhr.send("{" + "userName:'" + usernameOrEmailAddress + "'," + "passWord:'" + password + "'}"); //xhr.send("{" + "userName:'" + usernameOrEmailAddress + "'," + "passWord:'" + password + "'}");
}; };
function loginUserInternalRole(tenantId, callback) {
var usernameOrEmailAddress = document.getElementById('roleSelect').value;
//if (!usernameOrEmailAddress) {
// alert('UserName Can Not Be Null');
// return false;
//}
var password = document.getElementById('password').value;
var pwdMd5 = document.getElementById('pwdMd5').value;
console.log(pwdMd5);
if (!password && !pwdMd5) {
alert('PassWord And Md5 Can Not Be Null');
return false;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
// debugger;
if (xhr.status === 200) {
// debugger;
var resultdata = JSON.parse(xhr.responseText);
if (resultdata.ErrorMessage != '') {
alert(resultdata.ErrorMessage);
return false;
}
if (resultdata.code == 300) {
alert(resultdata.message);
}
else {
var responseJSON = JSON.parse(xhr.responseText);
var result = responseJSON;
var expireDate = new Date(Date.now() + (60 * 60 * 24 * 1000));
abp.auth.setToken(result.Result, expireDate);
callback();
}
} else {
alert('Login failed !');
}
}
};
xhr.open('get', `/User/loginSelectUserRole?userRoleId=${usernameOrEmailAddress}`, true);
xhr.setRequestHeader('Abp.TenantId', tenantId);
xhr.setRequestHeader('Content-type', 'application/json');
var authToken = abp.auth.getToken();
xhr.setRequestHeader('Authorization', `Bearer ${authToken}`);
xhr.send();
//xhr.send("{" + "userName:'" + usernameOrEmailAddress + "'," + "passWord:'" + password + "'}");
};
abp.swagger.login = function (callback) { abp.swagger.login = function (callback) {
//Get TenantId first //Get TenantId first
var tenancyName = document.getElementById('tenancyName').value; var tenancyName = document.getElementById('tenancyName').value;
@ -417,31 +356,6 @@ var abp = abp || {};
loginUserInternal(null, callback); // Login for host loginUserInternal(null, callback); // Login for host
} }
}; };
abp.swagger.loginRole = function (callback) {
//Get TenantId first
var tenancyName = document.getElementById('tenancyName').value;
if (tenancyName) {
var xhrTenancyName = new XMLHttpRequest();
xhrTenancyName.onreadystatechange = function () {
if (xhrTenancyName.readyState === XMLHttpRequest.DONE && xhrTenancyName.status === 200) {
var responseJSON = JSON.parse(xhrTenancyName.responseText);
var result = responseJSON.result;
if (result.state === 1) { // Tenant exists and active.
loginUserInternalRole(result.tenantId, callback); // Login for tenant
} else {
alert('There is no such tenant or tenant is not active !');
}
}
};
xhrTenancyName.open('POST', '/api/services/app/Account/IsTenantAvailable', true);
xhrTenancyName.setRequestHeader('Content-type', 'application/json');
xhrTenancyName.send("{" + "tenancyName:'" + tenancyName + "'}");
} else {
loginUserInternalRole(null, callback); // Login for host
}
};
abp.swagger.logout = function () { abp.swagger.logout = function () {
abp.auth.clearToken(); abp.auth.clearToken();
@ -505,10 +419,9 @@ var abp = abp || {};
//Inputs //Inputs
createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)'); createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'cyldev'); createInput(modalUxContent, 'userName', 'Username or email address','text','cyldev');
createInput(modalUxContent, 'password', 'Password', 'password', '123456'); createInput(modalUxContent, 'password', 'Password', 'password', '123456');
createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', ''); createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', '');
createSelect(modalUxContent, 'roleSelect', 'role', [])
//Buttons //Buttons
var authBtnWrapper = document.createElement('div'); var authBtnWrapper = document.createElement('div');
@ -531,18 +444,9 @@ var abp = abp || {};
abp.swagger.login(loginCallback); abp.swagger.login(loginCallback);
}; };
authBtnWrapper.appendChild(authorizeButton); authBtnWrapper.appendChild(authorizeButton);
// login role
var authorizeButton = document.createElement('button');
authorizeButton.className = 'btn modal-btn auth authorize button';
authorizeButton.innerText = 'LoginRole';
authorizeButton.onclick = function () {
abp.swagger.loginRole(loginCallback);
};
authBtnWrapper.appendChild(authorizeButton);
} }
function createInput(container, id, title, type, value = "") { function createInput(container, id, title, type, value="") {
var wrapper = document.createElement('div'); var wrapper = document.createElement('div');
wrapper.className = 'wrapper'; wrapper.className = 'wrapper';
if (id == "tenancyName") { if (id == "tenancyName") {
@ -566,31 +470,6 @@ var abp = abp || {};
input.autocomplete = "off"; input.autocomplete = "off";
section.appendChild(input);
}
function createSelect(container, id, title, option = []) {
var wrapper = document.createElement('div');
wrapper.className = 'wrapper';
if (id == "tenancyName") {
wrapper.style.display = 'none';
}
container.appendChild(wrapper);
var label = document.createElement('label');
label.innerText = title;
wrapper.appendChild(label);
var section = document.createElement('section');
section.className = 'block-tablet col-10-tablet block-desktop col-10-desktop';
wrapper.appendChild(section);
var input = document.createElement('select');
input.id = id;
input.style.width = '100%';
option.forEach(item => {
input.options.add(new Option(item.UserTypeShortName, item.Id));
})
section.appendChild(input); section.appendChild(input);
} }

View File

@ -0,0 +1,53 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Auth
{
public class IRaCISClaims
{
public Guid Id { get; set; }
public string FullName { get; set; } = String.Empty;
public string Code { get; set; } = String.Empty;
public string RealName { get; set; } = String.Empty;
public string UserTypeShortName { get; set; } = String.Empty;
public UserTypeEnum UserTypeEnum { get; set; }
public string PermissionStr { get; set; } = String.Empty;
public Guid UserTypeId { get; set; }
public int IsAdmin { get; }
public bool IsTestUser { get; set; }
public string Phone { get; set; } = String.Empty;
public static IRaCISClaims Create(UserBasicInfo user)
{
return new IRaCISClaims
{
Id = user.Id,
FullName = user.UserName,
RealName = user.RealName,
UserTypeEnum = user.UserTypeEnum,
UserTypeId = user.UserTypeId,
IsTestUser = user.IsTestUser,
Code = user.Code,
PermissionStr = user.PermissionStr,
UserTypeShortName = user.UserTypeShortName
};
}
public static IRaCISClaims Create(DoctorAccountDTO doctor)
{
return new IRaCISClaims
{
Id = doctor.Id,
FullName = doctor.FirstName + doctor.LastName,
Phone = doctor.Phone,
};
}
}
}

View File

@ -23,7 +23,7 @@ namespace IRaCIS.Core.Application.Auth
/// <summary> /// <summary>
/// 过期时间 /// 过期时间
/// </summary> /// </summary>
public int TokenExpireMinute { get; set; } public int TokenExpireDays { get; set; }
//public Dictionary<string, object> Claims { get; set; } //public Dictionary<string, object> Claims { get; set; }

View File

@ -8,9 +8,7 @@ namespace IRaCIS.Core.Application.Auth
public interface ITokenService public interface ITokenService
{ {
string GetToken(UserTokenInfo user); string GetToken(IRaCISClaims user);
bool IsTokenExpired(string token);
} }
@ -23,22 +21,22 @@ namespace IRaCIS.Core.Application.Auth
_jwtSetting = option.Value; _jwtSetting = option.Value;
} }
public string GetToken(UserTokenInfo user) public string GetToken(IRaCISClaims user)
{ {
//创建用户身份标识,可按需要添加更多信息 //创建用户身份标识,可按需要添加更多信息
var claims = new Claim[] var claims = new Claim[]
{ {
new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtIRaCISClaimType.IdentityUserId, user.IdentityUserId.ToString()), new Claim(JwtIRaCISClaimType.Id, user.Id.ToString()),
new Claim(JwtIRaCISClaimType.UserRoleId, user.UserRoleId.ToString()), new Claim(JwtIRaCISClaimType.Name, user.FullName),
new Claim(JwtIRaCISClaimType.UserName, user.UserName), new Claim(JwtIRaCISClaimType.RealName, user.RealName),
new Claim(JwtIRaCISClaimType.FullName, user.FullName), new Claim(JwtIRaCISClaimType.Code,user.Code),
new Claim(JwtIRaCISClaimType.UserTypeId,user.UserTypeId.ToString()), new Claim(JwtIRaCISClaimType.UserTypeId,user.UserTypeId.ToString()),
new Claim(JwtIRaCISClaimType.UserTypeEnum,user.UserTypeEnum.ToString()), new Claim(JwtIRaCISClaimType.UserTypeEnum,user.UserTypeEnum.ToString()),
new Claim(JwtIRaCISClaimType.UserTypeEnumInt,((int)user.UserTypeEnum).ToString()), new Claim(JwtIRaCISClaimType.UserTypeEnumInt,((int)user.UserTypeEnum).ToString()),
new Claim(JwtIRaCISClaimType.UserTypeShortName,user.UserTypeShortName), new Claim(JwtIRaCISClaimType.UserTypeShortName,user.UserTypeShortName),
new Claim(JwtIRaCISClaimType.PermissionStr,user.PermissionStr), new Claim(JwtIRaCISClaimType.PermissionStr,user.PermissionStr),
new Claim(JwtIRaCISClaimType.IsZhiZhun,user.IsZhiZhun.ToString()),
new Claim(JwtIRaCISClaimType.IsTestUser,user.IsTestUser.ToString()) new Claim(JwtIRaCISClaimType.IsTestUser,user.IsTestUser.ToString())
}; };
@ -49,27 +47,13 @@ namespace IRaCIS.Core.Application.Auth
signingCredentials: _jwtSetting.Credentials, signingCredentials: _jwtSetting.Credentials,
claims: claims, claims: claims,
notBefore: DateTime.Now, notBefore: DateTime.Now,
expires: DateTime.Now.AddMinutes(_jwtSetting.TokenExpireMinute) expires: DateTime.Now.AddDays(_jwtSetting.TokenExpireDays)
); );
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken; return jwtToken;
} }
public bool IsTokenExpired(string token)
{
var handler = new JwtSecurityTokenHandler();
try
{
var jwtToken = handler.ReadJwtToken(token);
return jwtToken.ValidTo < DateTime.UtcNow;
}
catch
{
return true; // 无效 Token 也视为已过期
}
}
} }

View File

@ -1,27 +0,0 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Auth
{
public class UserTokenInfo
{
public Guid IdentityUserId { get; set; }
public Guid UserRoleId { get; set; }
public Guid UserTypeId { get; set; }
public UserTypeEnum UserTypeEnum { get; set; }
public string UserName { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public string PermissionStr { get; set; } = string.Empty;
public bool IsTestUser { get; set; }
public bool IsZhiZhun { get; set; }
public string UserTypeShortName { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,104 @@
using Hangfire;
using Hangfire.Storage;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.Service.BackGroundJob
{
public interface IIRaCISHangfireJob
{
//Task MemoryCacheTrialStatusAsync();
Task InitHangfireJobTaskAsync();
}
public class IRaCISCHangfireJob(ILogger<IRaCISCHangfireJob> _logger,
IRepository<Internationalization> _internationalizationRepository,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository
) : IIRaCISHangfireJob
{
public static string JsonFileFolder = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources);
public async Task InitHangfireJobTaskAsync()
{
_logger.LogInformation("项目启动 hangfire 任务初始化 执行开始~");
//初始化国际化
await InternationalizationHelper.InitInternationlizationDataAndWatchJsonFileAsync(_internationalizationRepository);
//创建邮件定时任务
await InitSysAndTrialCronJobAsync();
#region 废弃
////项目状态 立即加载到缓存中
//await MemoryCacheTrialStatusAsync();
////await MemoryCacheAnonymizeData();
////创建项目缓存 定时任务
//HangfireJobHelper.AddOrUpdateInitCronJob<IIRaCISHangfireJob>("RecurringJob_Cache_TrialState", t => t.MemoryCacheTrialStatusAsync(), Cron.Daily());
#endregion
_logger.LogInformation("项目启动 hangfire 任务初始化 执行结束");
}
public async Task InitSysAndTrialCronJobAsync()
{
//var deleteJobIdList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr != StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
// .Select(t => t.TrialId + "_" + t.Id)
// .ToListAsync();
//foreach (var jobId in deleteJobIdList)
//{
// HangfireJobHelper.RemoveCronJob(jobId);
//}
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.ToListAsync();
foreach (var task in taskInfoList)
{
//利用主键作为任务Id
var jobId = $"{task.TrialId}_{task.Id}";
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, task.TrialId, task.BusinessScenarioEnum, task.EmailCron);
}
var addOrUpdateJobIdList = taskInfoList.Select(t => $"{t.TrialId}_{t.Id}").ToList();
var list = JobStorage.Current.GetConnection().GetRecurringJobs().ToList();
//项目定时任务都在default 队列
//var dbJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Where(t => t.Queue == "default").Select(t => t.Id).ToList();
//var deleteList= dbJobIdList.Except(addOrUpdateJobIdList).ToList();
// foreach (var jobId in deleteList)
// {
// HangfireJobHelper.RemoveCronJob(jobId);
// }
}
}
}

View File

@ -2,30 +2,116 @@
using IRaCIS.Core.Application.Service.BusinessFilter; using IRaCIS.Core.Application.Service.BusinessFilter;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Panda.DynamicWebApi; using Panda.DynamicWebApi;
using Panda.DynamicWebApi.Attributes; using Panda.DynamicWebApi.Attributes;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using ZiggyCreatures.Caching.Fusion;
namespace IRaCIS.Core.Application.Service
namespace IRaCIS.Core.Application.Service;
[TypeFilter(typeof(UnifiedApiResultFilter))]
[Authorize, DynamicWebApi]
public class BaseService : IDynamicWebApi
{ {
#pragma warning disable CS8618
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
#region 非泛型版本
[TypeFilter(typeof(UnifiedApiResultFilter))]
[Authorize, DynamicWebApi]
public class BaseService : IBaseService, IDynamicWebApi
{ {
return new ResponseOutput<string>() public IMapper _mapper { get; set; }
.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 IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public IWebHostEnvironment _hostEnvironment { get; set; }
public IFusionCache _fusionCache { get; set; }
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
{
return new ResponseOutput<string>()
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
}
}
public interface IBaseService
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
[MemberNotNull(nameof(_hostEnvironment))]
public IWebHostEnvironment _hostEnvironment { get; set; }
[MemberNotNull(nameof(_fusionCache))]
public IFusionCache _fusionCache { get; set; }
}
#endregion
#region 泛型版本测试
public interface IBaseServiceTest<T> where T : Entity
{
[MemberNotNull(nameof(_mapper))]
public IMapper _mapper { get; set; }
[MemberNotNull(nameof(_userInfo))]
public IUserInfo _userInfo { get; set; }
[MemberNotNull(nameof(_localizer))]
public IStringLocalizer _localizer { get; set; }
[MemberNotNull(nameof(_fusionCache))]
public IFusionCache _fusionCache { get; set; }
}
[TypeFilter(typeof(UnifiedApiResultFilter))]
[Authorize, DynamicWebApi]
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
{
public IMapper _mapper { get; set; }
public IUserInfo _userInfo { get; set; }
public IStringLocalizer _localizer { get; set; }
public IFusionCache _fusionCache { 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,43 @@
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.BusinessFilter;
/// <summary>
/// 不生效,不知道为啥
/// </summary>
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
this._logger = logger;
}
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
httpContext.Response.ContentType = "application/json";
var ex = exception;
var errorInfo = $"Exception: {ex.Message}[{ex.StackTrace}]" + (ex.InnerException != null ? $" InnerException: {ex.InnerException.Message}[{ex.InnerException.StackTrace}]" : "");
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk($"{ex?.Message}"));
_logger.LogError(errorInfo);
// return true to signal that this exception is handled
return ValueTask.FromResult(false);
}
}

View File

@ -1,68 +0,0 @@
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.Filter;
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
var exception = context.Exception;
if (!context.ExceptionHandled)
{
if (exception.GetType().Name == "DbUpdateConcurrencyException")
{
//---并发更新,当前不允许该操作
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + exception.Message));
}
if (exception.GetType() == typeof(BusinessValidationFailedException))
{
var error = exception as BusinessValidationFailedException;
var info = string.Empty;
if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Localizer_Dev_Dic.ContainsKey(error!.LocalizedKey))
{
info = $"[{error!.LocalizedKey}]:{StaticData.Localizer_Dev_Dic[error!.LocalizedKey]}";
}
context.Result = new JsonResult(ResponseOutput.NotOk(exception.Message, "", error!.Code, localizedInfo: info));
}
else if (exception.GetType() == typeof(QueryBusinessObjectNotExistException))
{
context.Result = new JsonResult(ResponseOutput.NotOk(exception.Message, ApiResponseCodeEnum.DataNotExist));
}
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
}
context.ExceptionHandled = true;//标记当前异常已经被处理过了
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
_logger.LogError(errorInfo);
//_logger.LogError(exception, exception.Message);
}
else
{
//继续
}
}
}

View File

@ -1,189 +0,0 @@
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static IRaCIS.Core.Domain.Share.StaticData;
namespace IRaCIS.Core.Application.Filter;
public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 检查当前 endpoint 是否包含 TrialGlobalLimitAttribute 特性
var trialLimitAttribute = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<TrialGlobalLimitAttribute>();
if (trialLimitAttribute != null)
{
var optActions = trialLimitAttribute._optActions;
// 这里开始执行原有的逻辑
var requestHost = context.HttpContext.Request.Host;
// 检查请求是否来自 localhost:6100
//if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
//{
// await next();
// return;
//}
#region 特殊用户类型拦截
// 用户类型检查
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
{
//对不起,您的账户没有操作权限
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_NoAccessPermission")));
return;
}
#endregion
#region 从多种途径取TrialId
//TrialId 传递的途径多种可能在path 可能在body 可能在数组中也可能在对象中可能就在url
var trialIdStr = string.Empty;
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
{
trialIdStr = context.HttpContext.Request.Query["trialId"];
}
//先尝试从path中取TrialId
else if (context.HttpContext.Request.RouteValues.Keys.Any(t => t.Contains("trialId")))
{
var index = context.HttpContext.Request.RouteValues.Keys.ToList().IndexOf("trialId");
trialIdStr = context.HttpContext.Request.RouteValues.Values.ToList()[index] as string;
}
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
{
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
var trialIdIndex = headerStr.IndexOf("trialId");
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
if (matchResult.Success)
{
trialIdStr = matchResult.Value;
}
else
{
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
return;
}
}
else
{
#region body 中取数据
//设置可以多次读
context.HttpContext.Request.EnableBuffering();
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.HttpContext.Request.Body);
var contentFromBody = await reader.ReadToEndAsync();
//读取后,流的位置还原
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
//context.HttpContext.Request.Body.Position = 0;
//找到参数位置在字符串中的索引
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
if (trialIdIndex > -1)
{
// (?<="trialId" *: *").*?(?=",)
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
if (matchResult.Success)
{
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
trialIdStr = matchResult.Value;
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
if (string.IsNullOrWhiteSpace(trialStatusStr))
{
//数据库 检查该项目Id不对
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
return;
}
}
else
{
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
return;
}
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
}
#endregion
}
#endregion
//通过path 或者body 找到trialId 了
if (!string.IsNullOrWhiteSpace(trialIdStr))
{
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
{
await next();
}
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
}
}
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
else if (optActions.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
{
await next();
}
else
{
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
//---该接口参数中,没有传递项目编号,请核对。
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_MissingProjectNumber")));
}
}
else
{
// 如果没有 TrialGlobalLimitAttribute则直接执行后续逻辑
await next();
}
}
}

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