From 288dacf6135b680bca205dfed84795574f12732a Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 15 Oct 2024 15:08:09 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AE=A1=E9=81=93?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=20IExceptionHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 11 ++- .../BusinessFilter/GlobalExceptionHandler.cs | 43 ----------- .../ProjectExceptionFilter.cs | 66 ++++++++++++++++ .../MinimalAPI/GlobalExceptionHandler.cs | 73 ++++++++++++++++++ .../BusinessFilter/ProjectExceptionFilter.cs | 75 ------------------- .../IRaCIS.Core.Application.xml | 9 ++- IRaCIS.Core.Application/TestService.cs | 4 + 7 files changed, 158 insertions(+), 123 deletions(-) delete mode 100644 IRaCIS.Core.Application/BusinessFilter/GlobalExceptionHandler.cs create mode 100644 IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs create mode 100644 IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs delete mode 100644 IRaCIS.Core.Application/BusinessFilter/ProjectExceptionFilter.cs diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 5c555bd6e..daddbfb55 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -79,8 +79,8 @@ var _configuration = builder.Configuration; //手动注册服务 builder.Services.ConfigureServices(_configuration); -//异常处理 -//builder.Services.AddExceptionHandler(); +//minimal api 异常处理 +builder.Services.AddExceptionHandler(); //builder.Services.AddProblemDetails(); //健康检查 @@ -148,7 +148,9 @@ builder.Services.AddMasaMinimalAPIs(options => options.DeletePrefixes = new List { "Delete", "Remove" }; options.RouteHandlerBuilder= t=> { - t.RequireAuthorization().AddEndpointFilter().WithGroupName("Institution"); + t.RequireAuthorization() + .AddEndpointFilter() + .WithGroupName("Institution"); }; options.DisableTrimMethodPrefix = true; //禁用去除方法前缀 options.DisableAutoMapRoute = false;//可通过配置true禁用全局自动路由映射或者删除此配置以启用全局自动路由映射 @@ -186,7 +188,8 @@ app.UseStatusCodePages(async context => }); -//app.UseExceptionHandler(o => { }); +//app.UseExceptionHandler(); +app.UseExceptionHandler(o => { }); //这里没生效,原因未知,官方文档也是这种写法,也用了GlobalExceptionHandler 尝试,还是不行,怀疑框架bug //app.UseExceptionHandler(configure => diff --git a/IRaCIS.Core.Application/BusinessFilter/GlobalExceptionHandler.cs b/IRaCIS.Core.Application/BusinessFilter/GlobalExceptionHandler.cs deleted file mode 100644 index 70688e7e2..000000000 --- a/IRaCIS.Core.Application/BusinessFilter/GlobalExceptionHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - - -namespace IRaCIS.Core.Application.BusinessFilter; - - -/// -/// 不生效,不知道为啥 -/// -public class GlobalExceptionHandler : IExceptionHandler -{ - private readonly ILogger _logger; - public GlobalExceptionHandler(ILogger logger) - { - this._logger = logger; - } - public ValueTask 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); - } -} diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs new file mode 100644 index 000000000..997aae9c8 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs @@ -0,0 +1,66 @@ +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 _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.Log_Locoalize_Dic.ContainsKey(error!.LocalizedKey)) + { + info = $"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_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); + + } + else + { + //继续 + } + } +} diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs new file mode 100644 index 000000000..688c2cd24 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs @@ -0,0 +1,73 @@ +using DocumentFormat.OpenXml.InkML; +using IRaCIS.Core.Infrastructure; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + + +namespace IRaCIS.Core.Application.BusinessFilter; + + +/// +/// minimal api 生效,但是传统控制器,没生效 +/// 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0 +/// +public class GlobalExceptionHandler(IStringLocalizer _localizer, ILogger _logger) : IExceptionHandler +{ + + public ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken) + { + + httpContext.Response.StatusCode =StatusCodes.Status200OK; + + httpContext.Response.ContentType = "application/json"; + + if (exception.GetType().Name == "DbUpdateConcurrencyException") + { + //---并发更新,当前不允许该操作 + + httpContext.Response.WriteAsJsonAsync(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.Log_Locoalize_Dic.ContainsKey(error!.LocalizedKey)) + { + info = $"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}"; + } + + httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(exception.Message, "", error!.Code, localizedInfo: info)); + + + + //warning 级别记录 + //_logger.LogWarning($"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}"); + } + else if (exception.GetType() == typeof(QueryBusinessObjectNotExistException)) + { + httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(exception.Message, ApiResponseCodeEnum.DataNotExist)); + } + else + { + httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message ) + : (exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException)); + + } + + var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : ""); + + _logger.LogError(errorInfo); + + // Return false to continue with the default behavior + // - or - return true to signal that this exception is handled + return ValueTask.FromResult(false); + } +} diff --git a/IRaCIS.Core.Application/BusinessFilter/ProjectExceptionFilter.cs b/IRaCIS.Core.Application/BusinessFilter/ProjectExceptionFilter.cs deleted file mode 100644 index 1825c3728..000000000 --- a/IRaCIS.Core.Application/BusinessFilter/ProjectExceptionFilter.cs +++ /dev/null @@ -1,75 +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 : Attribute, IExceptionFilter -{ - private readonly ILogger _logger; - - public IStringLocalizer _localizer; - - public ProjectExceptionFilter(IStringLocalizer localizer, ILogger 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; - - var info = string.Empty; - - if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Log_Locoalize_Dic.ContainsKey(error!.LocalizedKey)) - { - info = $"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}"; - } - - - context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, "", error!.Code, localizedInfo: info)); - - - //warning 级别记录 - //_logger.LogWarning($"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}"); - } - 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)); - - } - - context.ExceptionHandled = true;//标记当前异常已经被处理过了 - - - //throw new Exception("test-result-exceptioin"); - - } - else - { - //继续 - } - } -} diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 9924157c4..7900d19a5 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -12717,7 +12717,14 @@ - 不生效,不知道为啥 + minimal api 生效,但是传统控制器,没生效 + 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0 + + + + + minimal api 生效,但是传统控制器,没生效 + 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0 diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 0ed9d9fb7..727dfb5b5 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -58,6 +58,8 @@ namespace IRaCIS.Core.Application.Service public IResponseOutput GetTest() { + throw new BusinessValidationFailedException("手动抛出的异常"); + return ResponseOutput.Ok(_userInfo.IP); } } @@ -167,6 +169,8 @@ namespace IRaCIS.Core.Application.Service public async Task TestJson() { + throw new BusinessValidationFailedException("传统控制器异常"); + var model1 = new TestModel() { TestId = NewId.NextSequentialGuid(), TestName = null }; var model2 = new TestModel2() { TestId = NewId.NextSequentialGuid(), TestName = "test2" }; From 74a0c8923c7a5f81d3b78ce0fd2a615f775ca245 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Tue, 15 Oct 2024 15:42:08 +0800 Subject: [PATCH 2/9] =?UTF-8?q?minimal=20api=20=E8=B8=A2=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=B8=8B=E7=BA=BF=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 22 +---- .../EncreptApiResultFilter.cs | 0 .../LimitUserRequestAuthorization.cs | 20 +---- .../ModelActionFilter .cs | 0 .../{ => LegacyController}/ModelBinding.cs | 0 .../TrialResourceFilter.cs | 0 ...tUserRequestAuthorizationEndpointFilter.cs | 85 +++++++++++++++++++ .../UnifiedApiResultEndpointFilter.cs | 18 ++-- IRaCIS.Core.Application/TestService.cs | 2 +- 9 files changed, 99 insertions(+), 48 deletions(-) rename IRaCIS.Core.Application/BusinessFilter/{Encryption => LegacyController}/EncreptApiResultFilter.cs (100%) rename IRaCIS.Core.Application/BusinessFilter/{ => LegacyController}/LimitUserRequestAuthorization.cs (86%) rename IRaCIS.Core.Application/BusinessFilter/{ => LegacyController}/ModelActionFilter .cs (100%) rename IRaCIS.Core.Application/BusinessFilter/{ => LegacyController}/ModelBinding.cs (100%) rename IRaCIS.Core.Application/BusinessFilter/{ => LegacyController}/TrialResourceFilter.cs (100%) create mode 100644 IRaCIS.Core.Application/BusinessFilter/MinimalAPI/LimitUserRequestAuthorizationEndpointFilter.cs diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index daddbfb55..8aad500c0 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -149,6 +149,7 @@ builder.Services.AddMasaMinimalAPIs(options => options.RouteHandlerBuilder= t=> { t.RequireAuthorization() + .AddEndpointFilter() .AddEndpointFilter() .WithGroupName("Institution"); }; @@ -191,27 +192,6 @@ app.UseStatusCodePages(async context => //app.UseExceptionHandler(); app.UseExceptionHandler(o => { }); -//这里没生效,原因未知,官方文档也是这种写法,也用了GlobalExceptionHandler 尝试,还是不行,怀疑框架bug -//app.UseExceptionHandler(configure => -//{ -// configure.Run(async context => -// { -// var exceptionHandlerPathFeature = context.Features.Get(); -//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); - -//} -// }); -//}); - #region 暂时废弃 //app.UseMiddleware(); diff --git a/IRaCIS.Core.Application/BusinessFilter/Encryption/EncreptApiResultFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/EncreptApiResultFilter.cs similarity index 100% rename from IRaCIS.Core.Application/BusinessFilter/Encryption/EncreptApiResultFilter.cs rename to IRaCIS.Core.Application/BusinessFilter/LegacyController/EncreptApiResultFilter.cs diff --git a/IRaCIS.Core.Application/BusinessFilter/LimitUserRequestAuthorization.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/LimitUserRequestAuthorization.cs similarity index 86% rename from IRaCIS.Core.Application/BusinessFilter/LimitUserRequestAuthorization.cs rename to IRaCIS.Core.Application/BusinessFilter/LegacyController/LimitUserRequestAuthorization.cs index ec5ea0863..69bafe0f9 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LimitUserRequestAuthorization.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/LimitUserRequestAuthorization.cs @@ -13,24 +13,10 @@ namespace IRaCIS.Core.Application.Filter; -public class LimitUserRequestAuthorization : IAsyncAuthorizationFilter +public class LimitUserRequestAuthorization( + IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer, + IOptionsMonitor _verifyConfig) : IAsyncAuthorizationFilter { - public IStringLocalizer _localizer { get; set; } - - private readonly IFusionCache _fusionCache; - - private readonly IUserInfo _userInfo; - - private readonly IOptionsMonitor _verifyConfig; - - public LimitUserRequestAuthorization(IFusionCache fusionCache, IUserInfo userInfo, IStringLocalizer localizer, IOptionsMonitor verifyConfig) - { - _fusionCache = fusionCache; - _userInfo = userInfo; - _verifyConfig = verifyConfig; - _localizer = localizer; - } - public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { diff --git a/IRaCIS.Core.Application/BusinessFilter/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs similarity index 100% rename from IRaCIS.Core.Application/BusinessFilter/ModelActionFilter .cs rename to IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs diff --git a/IRaCIS.Core.Application/BusinessFilter/ModelBinding.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs similarity index 100% rename from IRaCIS.Core.Application/BusinessFilter/ModelBinding.cs rename to IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs diff --git a/IRaCIS.Core.Application/BusinessFilter/TrialResourceFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/TrialResourceFilter.cs similarity index 100% rename from IRaCIS.Core.Application/BusinessFilter/TrialResourceFilter.cs rename to IRaCIS.Core.Application/BusinessFilter/LegacyController/TrialResourceFilter.cs diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/LimitUserRequestAuthorizationEndpointFilter.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/LimitUserRequestAuthorizationEndpointFilter.cs new file mode 100644 index 000000000..0d7a9f5a3 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/LimitUserRequestAuthorizationEndpointFilter.cs @@ -0,0 +1,85 @@ +using IRaCIS.Core.Application.Helper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.Service.BusinessFilter; + +#region minimalapi 流程 + + +public class LimitUserRequestAuthorizationEndpointFilter( + IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer, + IOptionsMonitor _verifyConfig) : IEndpointFilter +{ + + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + var minutes = _verifyConfig.CurrentValue.AutoLoginOutMinutes; + + if (_verifyConfig.CurrentValue.OpenLoginLimit) + { + // 如果此请求允许匿名访问,不进行处理 + var endpoint = context.HttpContext.GetEndpoint(); + if (endpoint?.Metadata?.GetMetadata() != null) + { + return await next(context); + } + + // 没有从请求中取到 token + if (string.IsNullOrWhiteSpace(_userInfo.UserToken)) + { + context.HttpContext.Response.ContentType = "application/json"; + return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AuthTokenMissing"]), statusCode: StatusCodes.Status200OK); + } + + // 获取缓存中的用户 token + var cacheUserToken = await _fusionCache.GetOrDefaultAsync(CacheKeys.UserToken(_userInfo.Id)); + + // 缓存中没有取到 token + if (string.IsNullOrWhiteSpace(cacheUserToken)) + { + // 设置当前用户最新 token + await _fusionCache.SetAsync(CacheKeys.UserToken(_userInfo.Id), _userInfo.UserToken, TimeSpan.FromDays(7)); + await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes)); + } + // 如果是同一个用户 + else if (cacheUserToken == _userInfo.UserToken) + { + var cacheTime = await _fusionCache.GetOrDefaultAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id)); + + // 如果过期,自动登出 + if (string.IsNullOrEmpty(cacheTime)) + { + context.HttpContext.Response.ContentType = "application/json"; + return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountAuto_LoginOut"], ApiResponseCodeEnum.AutoLoginOut), statusCode: StatusCodes.Status403Forbidden); + } + else + { + await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes)); + } + } + else + { + // 如果账户在其他地方已登录,当前用户被迫下线 + context.HttpContext.Response.ContentType = "application/json"; + return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountLoggedInElsewhere"], ApiResponseCodeEnum.LoginInOtherPlace), statusCode: StatusCodes.Status403Forbidden); + } + } + + // 如果通过授权,继续执行请求管道 + return await next(context); + + } + + +} + +#endregion \ No newline at end of file diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/UnifiedApiResultEndpointFilter.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/UnifiedApiResultEndpointFilter.cs index 559c9f3ab..008655bc5 100644 --- a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/UnifiedApiResultEndpointFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/UnifiedApiResultEndpointFilter.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -11,14 +12,8 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter; #region minimalapi 流程 -public class UnifiedApiResultEndpointFilter : IEndpointFilter +public class UnifiedApiResultEndpointFilter(ILogger _logger) : IEndpointFilter { - private readonly ILogger _logger; - - public UnifiedApiResultEndpointFilter(ILogger logger) - { - _logger = logger; - } public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { @@ -37,8 +32,13 @@ public class UnifiedApiResultEndpointFilter : IEndpointFilter return ResponseOutput.Ok(tuple.Item1, tuple.Item2); } - if (result is IResponseOutput) - { + if (result is IResponseOutput responseOutput) + { + if (!string.IsNullOrWhiteSpace(responseOutput.LocalizedInfo)) + { + //统一在这里记录国际化的日志信息 + _logger.LogWarning($"{responseOutput.LocalizedInfo}"); + } return result; } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 727dfb5b5..b33c86a67 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -58,7 +58,7 @@ namespace IRaCIS.Core.Application.Service public IResponseOutput GetTest() { - throw new BusinessValidationFailedException("手动抛出的异常"); + //throw new BusinessValidationFailedException("手动抛出的异常"); return ResponseOutput.Ok(_userInfo.IP); } From 8be3cc9e78d7ed651b7d588fcbe7370f8bd98556 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 16 Oct 2024 09:03:12 +0800 Subject: [PATCH 3/9] =?UTF-8?q?minimal=20api=20=E6=A8=A1=E5=9E=8B=E9=AA=8C?= =?UTF-8?q?=E8=AF=81+=20=E6=96=B9=E6=B3=95=E7=BA=A7=E5=88=AB=E7=9A=84?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=E6=9A=82=E6=97=B6=E6=9C=AA=E6=9C=89?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/UploadDownLoadController.cs | 24 ++++++++ IRaCIS.Core.API/Progranm.cs | 2 +- .../LegacyController/ModelActionFilter .cs | 8 +-- .../LegacyController/ModelBinding.cs | 26 --------- .../ModelValidationEndpointFilter.cs | 58 +++++++++++++++++++ .../TrialGlobalLimitEndpointFilter.cs | 17 ++++++ IRaCIS.Core.Application/TestService.cs | 3 +- 7 files changed, 104 insertions(+), 34 deletions(-) delete mode 100644 IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs create mode 100644 IRaCIS.Core.Application/BusinessFilter/MinimalAPI/ModelValidationEndpointFilter.cs create mode 100644 IRaCIS.Core.Application/BusinessFilter/MinimalAPI/TrialGlobalLimitEndpointFilter.cs diff --git a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs index 847a069bb..c011f1f40 100644 --- a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs +++ b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs @@ -18,6 +18,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.WebUtilities; @@ -43,6 +45,28 @@ namespace IRaCIS.Core.API.Controllers #region 上传基类封装 + + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter + { + public void OnResourceExecuting(ResourceExecutingContext context) + { + + + var factories = context.ValueProviderFactories; + //factories.RemoveType(); + factories.RemoveType(); + factories.RemoveType(); + context.HttpContext.Request.EnableBuffering(); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + } + } + + [DisableFormValueModelBinding] public abstract class UploadBaseController : ControllerBase { diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 8aad500c0..48e5a45dd 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -91,7 +91,6 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() builder.Services.AddControllers(options => { - //options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); @@ -150,6 +149,7 @@ builder.Services.AddMasaMinimalAPIs(options => options.RouteHandlerBuilder= t=> { t.RequireAuthorization() .AddEndpointFilter() + .AddEndpointFilter() .AddEndpointFilter() .WithGroupName("Institution"); }; diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index a52ca0a52..63255f4fa 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -8,13 +8,9 @@ namespace IRaCIS.Core.Application.Filter; -public class ModelActionFilter : ActionFilterAttribute, IActionFilter +public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter { - public IStringLocalizer _localizer; - public ModelActionFilter(IStringLocalizer localizer) - { - _localizer = localizer; - } + public override void OnActionExecuting(ActionExecutingContext context) { diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs deleted file mode 100644 index 994d9fa09..000000000 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelBinding.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace IRaCIS.Core.Application.Filter; - -#region snippet_DisableFormValueModelBindingAttribute -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter -{ - public void OnResourceExecuting(ResourceExecutingContext context) - { - - - var factories = context.ValueProviderFactories; - //factories.RemoveType(); - factories.RemoveType(); - factories.RemoveType(); - context.HttpContext.Request.EnableBuffering(); - } - - public void OnResourceExecuted(ResourceExecutedContext context) - { - } -} -#endregion diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/ModelValidationEndpointFilter.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/ModelValidationEndpointFilter.cs new file mode 100644 index 000000000..32af30c35 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/ModelValidationEndpointFilter.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.BusinessFilter; + +public class ModelValidationEndpointFilter(IStringLocalizer _localizer) : IEndpointFilter +{ + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + var httpContext = context.HttpContext; + + //// 1. 获取路由参数并验证 + //foreach (var routeValue in httpContext.Request.RouteValues) + //{ + // { + // return Results.BadRequest(new { ErrorMessage = $"Invalid format for {routeValue.Key}." }); + // } + //} + + //// 2. 获取查询参数并验证 + //foreach (var queryValue in httpContext.Request.Query) + //{ + // { + // return Results.BadRequest(new { ErrorMessage = $"Invalid format for {queryValue.Key}." }); + // } + //} + + + // 获取请求中的模型参数 + var model = context.Arguments.FirstOrDefault(a => a != null && a.GetType().IsClass); + + // 仅当模型存在时执行验证 + if (model != null) + { + var validationResults = new List(); + var validationContext = new ValidationContext(model); + bool isValid = Validator.TryValidateObject(model, validationContext, validationResults, true); + + if (!isValid) + { + var validationErrors = validationResults.Select(vr => vr.ErrorMessage).ToArray(); + return Results.Json(new + { + ErrorMessage = _localizer["ModelAction_InvalidAPIParameter"], + Errors = validationErrors + }, statusCode: StatusCodes.Status400BadRequest); + } + } + + // 验证通过,继续执行 + return await next(context); + } +} diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/TrialGlobalLimitEndpointFilter.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/TrialGlobalLimitEndpointFilter.cs new file mode 100644 index 000000000..d50a6c102 --- /dev/null +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/TrialGlobalLimitEndpointFilter.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.BusinessFilter; + +public class TrialGlobalLimitEndpointFilter : IEndpointFilter + +{ + public ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + throw new NotImplementedException(); + } +} diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index b33c86a67..419a86ebb 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -56,7 +56,8 @@ namespace IRaCIS.Core.Application.Service return Task.FromResult(list); } - public IResponseOutput GetTest() + [AllowAnonymous] + public IResponseOutput GetTest(Guid trialId,int num, TestModel testModel) { //throw new BusinessValidationFailedException("手动抛出的异常"); From 663a6c7f5c7ab17e3353e9d44ac993dadd586ed5 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 16 Oct 2024 10:40:38 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=9D=99=E6=80=81?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96+=E6=B5=8B=E8=AF=95ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 4 +++ IRaCIS.Core.Application/TestService.cs | 14 +++++++-- .../Repository/IRepository.cs | 21 ++----------- .../Repository/Repository.cs | 6 ++-- .../_IRaCIS/Globalization/I18n.cs | 31 +++++++++++++++++++ 5 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 IRaCIS.Core.Infrastructure/_IRaCIS/Globalization/I18n.cs diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 48e5a45dd..c030e8517 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -17,6 +17,7 @@ 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; @@ -259,6 +260,9 @@ var hangfireJobService = app.Services.GetRequiredService(); await hangfireJobService.InitHangfireJobTaskAsync(); +var localizer = app.Services.GetRequiredService(); +I18n.SetLocalizer(localizer); + try { #region 运行环境 部署平台 diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 419a86ebb..e85e65479 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -28,7 +28,6 @@ using NPOI.XWPF.UserModel; using System.Globalization; using System.Runtime.InteropServices; using System.Text; -using Tea; namespace IRaCIS.Core.Application.Service @@ -57,12 +56,23 @@ namespace IRaCIS.Core.Application.Service } [AllowAnonymous] - public IResponseOutput GetTest(Guid trialId,int num, TestModel testModel) + public IResponseOutput GetTest() { //throw new BusinessValidationFailedException("手动抛出的异常"); return ResponseOutput.Ok(_userInfo.IP); } + + public IResponseOutput GetTestI18n() + { + var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + + //CultureInfo.CurrentCulture = new CultureInfo(StaticData.CultureInfo.en_US); + //CultureInfo.CurrentUICulture = new CultureInfo(StaticData.CultureInfo.en_US); + + return ResponseOutput.Ok(I18n.T("TaskAllocation_DoctorConfigExists")); + } + } diff --git a/IRaCIS.Core.Infra.EFCore/Repository/IRepository.cs b/IRaCIS.Core.Infra.EFCore/Repository/IRepository.cs index bd33d1a93..ddecbf4d5 100644 --- a/IRaCIS.Core.Infra.EFCore/Repository/IRepository.cs +++ b/IRaCIS.Core.Infra.EFCore/Repository/IRepository.cs @@ -71,23 +71,9 @@ namespace IRaCIS.Core.Infra.EFCore bool autoSave = false, bool ignoreQueryFilter = false) where T : Entity; } - public class Repository : IRepository + public class Repository(IRaCISDBContext _dbContext, IMapper _mapper, IUserInfo _userInfo) : IRepository { - #region 构造 基本 - private IRaCISDBContext _dbContext { get; } - public IMapper _mapper { get; set; } - - public IUserInfo _userInfo { get; set; } - - public IStringLocalizer _localizer { get; set; } - - public Repository(IRaCISDBContext dbContext, IMapper mapper, IUserInfo userInfo, IStringLocalizer localizer) - { - _localizer = localizer; - _dbContext = dbContext; - _mapper = mapper; - _userInfo = userInfo; - } + /// /// 设置是使用哪个仓储 默认不跟踪 @@ -171,7 +157,7 @@ namespace IRaCIS.Core.Infra.EFCore if (dbEntity == null) { - throw new BusinessValidationFailedException(_localizer["Repository_UpdateError"]); + throw new BusinessValidationFailedException(I18n.T("Repository_UpdateError")); } var dbBeforEntity = dbEntity.Clone(); @@ -450,7 +436,6 @@ namespace IRaCIS.Core.Infra.EFCore } - #endregion diff --git a/IRaCIS.Core.Infra.EFCore/Repository/Repository.cs b/IRaCIS.Core.Infra.EFCore/Repository/Repository.cs index d27e4021f..98b87f116 100644 --- a/IRaCIS.Core.Infra.EFCore/Repository/Repository.cs +++ b/IRaCIS.Core.Infra.EFCore/Repository/Repository.cs @@ -100,7 +100,7 @@ namespace IRaCIS.Core.Infra.EFCore if (dbEntity == null) { - throw new BusinessValidationFailedException(_localizer["Repository_UpdateError"]); + throw new BusinessValidationFailedException(I18n.T("Repository_UpdateError")); } var dbBeforEntity = dbEntity.Clone(); @@ -156,7 +156,7 @@ namespace IRaCIS.Core.Infra.EFCore if (searchEntity == null) { - throw new BusinessValidationFailedException(_localizer["Repository_UpdateError"]); + throw new BusinessValidationFailedException(I18n.T("Repository_UpdateError")); } _dbContext.EntityModifyPartialFiled(searchEntity, updateFactory); @@ -208,7 +208,7 @@ namespace IRaCIS.Core.Infra.EFCore if (waitDelete == null) { - throw new BusinessValidationFailedException(_localizer["Repository_DeleteError"]); + throw new BusinessValidationFailedException(I18n.T("Repository_DeleteError")); } await DeleteAsync(waitDelete, autoSave); diff --git a/IRaCIS.Core.Infrastructure/_IRaCIS/Globalization/I18n.cs b/IRaCIS.Core.Infrastructure/_IRaCIS/Globalization/I18n.cs new file mode 100644 index 000000000..58e54d4d4 --- /dev/null +++ b/IRaCIS.Core.Infrastructure/_IRaCIS/Globalization/I18n.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + + namespace IRaCIS.Core.Infrastructure.Extention; + +public static class I18n +{ + private static IStringLocalizer _localizer; + + public static void SetLocalizer(IStringLocalizer localizer) + { + _localizer = localizer; + } + + + + public static string T(string key) + { + return _localizer[key]; + } + + public static string T(string key, params object[] arguments) + { + return _localizer[key, arguments]; + } + +} From 7d07e18853f6ee4a50e16899a0d01ba36d354faf Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 16 Oct 2024 11:06:17 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9D=99=E6=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=9B=BD=E9=99=85=E5=8C=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusinessFilter/UnifiedApiResultFilter.cs | 2 +- .../ProjectExceptionFilter.cs | 4 +- .../UnifiedApiResultFilter.cs | 2 +- .../MinimalAPI/GlobalExceptionHandler.cs | 4 +- .../Helper/FileStoreHelper.cs | 6 +-- .../Helper/InternationalizationHelper.cs | 10 ++--- .../Helper/SendEmailHelper.cs | 4 +- .../ImageAndDoc/DownloadAndUploadService.cs | 2 +- .../Service/ImageAndDoc/StudyService.cs | 4 +- IRaCIS.Core.Domain/GlobalUsings.cs | 1 + .../ReadingQuestionCriterionTrial.cs | 2 +- IRaCIS.Core.Domain/Trial/Trial.cs | 2 +- .../_IRaCIS/Output/ResponseOutput.cs | 4 +- .../_IRaCIS/_Config/_StaticData.cs | 44 +++++-------------- 14 files changed, 35 insertions(+), 56 deletions(-) diff --git a/IRC.Core.SCP/BusinessFilter/UnifiedApiResultFilter.cs b/IRC.Core.SCP/BusinessFilter/UnifiedApiResultFilter.cs index 4dffb183c..69af4a5f9 100644 --- a/IRC.Core.SCP/BusinessFilter/UnifiedApiResultFilter.cs +++ b/IRC.Core.SCP/BusinessFilter/UnifiedApiResultFilter.cs @@ -77,7 +77,7 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter else if(statusCode != 200&&!(objectResult.Value is IResponseOutput)) { //---程序错误,请联系开发人员。 - var apiResponse = ResponseOutput.NotOk(StaticData.International("UnifiedAPI_ProgramError")); + var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError")); objectResult.Value = apiResponse; objectResult.DeclaredType = apiResponse.GetType(); diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs index 997aae9c8..444c1dfbd 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs @@ -30,9 +30,9 @@ public class ProjectExceptionFilter(ILogger _logger, ISt var info = string.Empty; - if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Log_Locoalize_Dic.ContainsKey(error!.LocalizedKey)) + if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Localizer_Dev_Dic.ContainsKey(error!.LocalizedKey)) { - info = $"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}"; + info = $"[{error!.LocalizedKey}]:{StaticData.Localizer_Dev_Dic[error!.LocalizedKey]}"; } diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs index a1997eabc..bba090181 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs @@ -83,7 +83,7 @@ public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter else if (statusCode != 200 && !(objectResult.Value is IResponseOutput)) { //---程序错误,请联系开发人员。 - var apiResponse = ResponseOutput.NotOk(StaticData.International("UnifiedAPI_ProgramError")); + var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError")); objectResult.Value = apiResponse; objectResult.DeclaredType = apiResponse.GetType(); diff --git a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs index 688c2cd24..1cdec4619 100644 --- a/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs +++ b/IRaCIS.Core.Application/BusinessFilter/MinimalAPI/GlobalExceptionHandler.cs @@ -39,9 +39,9 @@ public class GlobalExceptionHandler(IStringLocalizer _localizer, ILogger(CacheKeys.TrialTaskStudyUidDBLock(incommand.TrialId, incommand.VisitTaskId, incommand.Study.StudyInstanceUid)) != Guid.Empty) { //---当前已有人正在上传和归档该检查! - return ResponseOutput.NotOk(StaticData.International("UploadDownLoad_ArchiveInProgress")); + return ResponseOutput.NotOk(I18n.T("UploadDownLoad_ArchiveInProgress")); } else { diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index ce3ab2dbb..3fc485f84 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -102,7 +102,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc if (cacheValue != Guid.Empty && cacheValue != _userInfo.Id) { //---当前已有人正在上传和归档该检查! - return ResponseOutput.NotOk(StaticData.International("UploadDownLoad_ArchiveInProgress")); + return ResponseOutput.NotOk(I18n.T("UploadDownLoad_ArchiveInProgress")); } else { @@ -134,7 +134,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc if (_fusionCache.GetOrDefault(CacheKeys.TrialStudyUidDBLock(incommand.TrialId, incommand.Study.StudyInstanceUid)) != Guid.Empty) { //---当前已有人正在上传和归档该检查! - return ResponseOutput.NotOk(StaticData.International("UploadDownLoad_ArchiveInProgress")); + return ResponseOutput.NotOk(I18n.T("UploadDownLoad_ArchiveInProgress")); } else { diff --git a/IRaCIS.Core.Domain/GlobalUsings.cs b/IRaCIS.Core.Domain/GlobalUsings.cs index c7a91c2b9..8970cf57c 100644 --- a/IRaCIS.Core.Domain/GlobalUsings.cs +++ b/IRaCIS.Core.Domain/GlobalUsings.cs @@ -4,3 +4,4 @@ global using System; global using System.Collections.Generic; global using System.ComponentModel.DataAnnotations; global using System.ComponentModel.DataAnnotations.Schema; +global using IRaCIS.Core.Infrastructure.Extention; diff --git a/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs b/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs index 51e0a0d57..3a2d77ac4 100644 --- a/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs +++ b/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs @@ -62,7 +62,7 @@ public class ReadingQuestionCriterionTrial : BaseAddAuditEntity [Comment("全局阅片评估更新类型")] public string GlobalUpdateType { get; set; } = string.Empty; [Comment("评估原因")] - public string EvaluationReason { get; set; } = StaticData.International("CriterionTrial_EvaluationReason"); + public string EvaluationReason { get; set; } = I18n.T("CriterionTrial_EvaluationReason"); [Comment("是否显示详情")] public bool IsShowDetail { get; set; } = true; diff --git a/IRaCIS.Core.Domain/Trial/Trial.cs b/IRaCIS.Core.Domain/Trial/Trial.cs index d112c6cf7..cba96ac82 100644 --- a/IRaCIS.Core.Domain/Trial/Trial.cs +++ b/IRaCIS.Core.Domain/Trial/Trial.cs @@ -132,7 +132,7 @@ public partial class Trial : BaseFullDeleteAuditEntity public bool VisitPlanConfirmed { get; set; } [Comment("受试者编号具体规则")] - public string SubjectCodeRule { get; set; } = StaticData.International("Trial_number"); + public string SubjectCodeRule { get; set; } = I18n.T("Trial_number"); [Comment("是否 提醒受试者编号规则")] public bool IsNoticeSubjectCodeRule { get; set; } = true; diff --git a/IRaCIS.Core.Infrastructure/_IRaCIS/Output/ResponseOutput.cs b/IRaCIS.Core.Infrastructure/_IRaCIS/Output/ResponseOutput.cs index 9023c87e5..d4ceae5be 100644 --- a/IRaCIS.Core.Infrastructure/_IRaCIS/Output/ResponseOutput.cs +++ b/IRaCIS.Core.Infrastructure/_IRaCIS/Output/ResponseOutput.cs @@ -117,9 +117,9 @@ namespace IRaCIS.Core.Infrastructure.Extention var info = string.Empty; - if (!string.IsNullOrWhiteSpace(key) && StaticData.Log_Locoalize_Dic.ContainsKey(key)) + if (!string.IsNullOrWhiteSpace(key) && StaticData.Localizer_Dev_Dic.ContainsKey(key)) { - info = $"[{key}]:{StaticData.Log_Locoalize_Dic[key]}"; + info = $"[{key}]:{StaticData.Localizer_Dev_Dic[key]}"; } return new ResponseOutput().NotOk(msg, code: code, localizedInfo: info); diff --git a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs index d10de14af..b651aa78a 100644 --- a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs +++ b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; @@ -12,7 +13,7 @@ public static class StaticData public static Dictionary Zh_CN_Dic = new Dictionary(); - public static Dictionary Log_Locoalize_Dic = new Dictionary(); + public static Dictionary Localizer_Dev_Dic = new Dictionary(); #region 国际化 @@ -33,44 +34,21 @@ public static class StaticData /// /// /// - public static string International(string key, params object?[] args) + public static string I18n(string key, params object?[] args) { - try - { - return string.Format(GetLanguageDictionary(key), args); - } - catch (Exception) - { + var isEn_US = System.Globalization.CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; - return string.Empty; + if (isEn_US) + { + return En_US_Dic[key]; } + else + { + return Zh_CN_Dic[key]; + } } - public static string GetLanguageDictionary(string key) - { - try - { - var type = Thread.CurrentThread.CurrentUICulture.Name; - if (type == "zh-CN") - { - return Zh_CN_Dic[key]; - - } - else - { - return En_US_Dic[key]; - - } - } - catch (Exception) - { - - return string.Empty; - } - - } - #endregion #region 字典表项固定值 From ece98887daaa95072831a317df9a787e5462e22a Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 16 Oct 2024 15:27:57 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E7=B2=BE=E7=AE=80=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 1 + .../BackGroundJob/IRaCISCHangfireJob.cs | 30 +- .../Helper/InternationalizationHelper.cs | 261 +++++++----------- .../Common/InternationalizationService.cs | 2 +- .../Service/Common/_MapConfig.cs | 4 + .../_IRaCIS/_Config/_StaticData.cs | 48 ++-- 6 files changed, 140 insertions(+), 206 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index c030e8517..2e99be6b9 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -260,6 +260,7 @@ var hangfireJobService = app.Services.GetRequiredService(); await hangfireJobService.InitHangfireJobTaskAsync(); +//设置国际化I18n var localizer = app.Services.GetRequiredService(); I18n.SetLocalizer(localizer); diff --git a/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs b/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs index efd823af2..29fbf83ee 100644 --- a/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs +++ b/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs @@ -9,9 +9,6 @@ namespace IRaCIS.Core.Application.Service.BackGroundJob public interface IIRaCISHangfireJob { - - //Task MemoryCacheTrialStatusAsync(); - Task InitHangfireJobTaskAsync(); } @@ -29,32 +26,27 @@ namespace IRaCIS.Core.Application.Service.BackGroundJob //初始化国际化 - await InternationalizationHelper.InitInternationlizationDataAndWatchJsonFileAsync(_internationalizationRepository); + //查询数据库的数据 + 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); //创建邮件定时任务 await InitSysAndTrialCronJobAsync(); - #region 废弃 - ////项目状态 立即加载到缓存中 - //await MemoryCacheTrialStatusAsync(); - - ////await MemoryCacheAnonymizeData(); - - - ////创建项目缓存 定时任务 - //HangfireJobHelper.AddOrUpdateInitCronJob("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) diff --git a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs index 37ede0c0d..90c2c9120 100644 --- a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs +++ b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs @@ -4,203 +4,136 @@ using IRaCIS.Core.Infrastructure; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SharpCompress.Common; namespace IRaCIS.Core.Application.Helper; + +public class IRCGlobalInfoDTO +{ + public string Code { get; set; } + + public string Value { get; set; } + + public string ValueCN { get; set; } + + public string Description { get; set; } +} + public static class InternationalizationHelper { public static string JsonFileFolder = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources); - public static FileSystemWatcher FileSystemWatcher_US { get; set; } - public static FileSystemWatcher FileSystemWatcher_CN { get; set; } + public static string USJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); + public static string CNJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - private static void VerifyFolder() + static InternationalizationHelper() { if (!Directory.Exists(JsonFileFolder) || - !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))) - { - throw new BusinessValidationFailedException("国际化Json文件目录有误"); - } - } - - public static async Task BatchAddJsonKeyValueAsync(List batchAddDtos) - { - VerifyFolder(); - - var usJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); - var cnJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - - - //更新json 文件 同时更新内存缓存的数据 - foreach (var filePath in new string[] { usJsonPath, cnJsonPath }) - { - var json = await File.ReadAllTextAsync(filePath); - - JObject jsonObject = JObject.Parse(json, new JsonLoadSettings() { CommentHandling = CommentHandling.Load }); - - // 添加或更新指定的键值对 - - if (filePath.Contains(StaticData.En_US_Json)) - { - foreach (var item in batchAddDtos) - { - jsonObject[item.Code] = item.Value; - - StaticData.En_US_Dic[item.Code] = item.Value; - - //日志记录该信息方便自己人看, 返回给客户的是配置的 - StaticData.Localizer_Dev_Dic[item.Code] = item.Description; - } - } - else - { - foreach (var item in batchAddDtos) - { - jsonObject[item.Code] = item.Value; - - StaticData.Zh_CN_Dic[item.Code] = item.Value; - } - } - - - await File.WriteAllTextAsync(filePath, jsonObject.ToString()); - - } - } - - public static async Task AddOrUpdateJsonKeyValueAsync(string key, string value, string valueCN, string description) - { - - VerifyFolder(); - - var usJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); - var cnJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - - - //更新json 文件 同时更新内存缓存的数据 - foreach (var filePath in new string[] { usJsonPath, cnJsonPath }) - { - var json = await File.ReadAllTextAsync(filePath); - - JObject jsonObject = JObject.Parse(json, new JsonLoadSettings() { CommentHandling = CommentHandling.Load }); - - // 添加或更新指定的键值对 - - if (filePath.Contains(StaticData.En_US_Json)) - { - jsonObject[key] = value; - - StaticData.En_US_Dic[key] = value; - - //日志记录该信息方便自己人看, 返回给客户的是配置的 - StaticData.Localizer_Dev_Dic[key] = description; - } - else - { - jsonObject[key] = valueCN; - - StaticData.Zh_CN_Dic[key] = valueCN; - } - - - await File.WriteAllTextAsync(filePath, jsonObject.ToString()); - - } - - } - - - public static async Task InitInternationlizationDataAndWatchJsonFileAsync(IRepository _internationalizationRepository) - { - //查询数据库的数据 - var toJsonList = await _internationalizationRepository.Where(t => t.InternationalizationType == 1).Select(t => new - { - t.Code, - t.Value, - t.ValueCN, - t.Description - }).ToListAsync(); - - //组织成json 文件 - - var usJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); - var cnJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - - - //本地静态文件国际化需要 - foreach (var tojsonItem in toJsonList) - { - StaticData.En_US_Dic[tojsonItem.Code] = tojsonItem.Value; - StaticData.Zh_CN_Dic[tojsonItem.Code] = tojsonItem.ValueCN; - - //日志记录该信息方便自己人看, 返回给客户的是配置的 - StaticData.Localizer_Dev_Dic[tojsonItem.Code] = tojsonItem.Description; - } - - File.WriteAllText(usJsonPath, JsonConvert.SerializeObject(StaticData.En_US_Dic)); - File.WriteAllText(cnJsonPath, JsonConvert.SerializeObject(StaticData.Zh_CN_Dic)); - - - //监测Json文件变更 实时刷新数据 - - if (!File.Exists(usJsonPath) || !File.Exists(cnJsonPath)) + !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))|| + !File.Exists(USJsonPath) || !File.Exists(CNJsonPath)) { throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound")); } - - - //监测Json文件变更 实时刷新数据 - - - FileSystemWatcher_US = new FileSystemWatcher - { - Path = Path.GetDirectoryName(usJsonPath)!, - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size, - Filter = Path.GetFileName(usJsonPath), - EnableRaisingEvents = true, - - }; - // 添加文件更改事件的处理程序 - FileSystemWatcher_US.Changed += (sender, e) => LoadJsonFile(StaticData.Folder.Resources + "\\" + StaticData.En_US_Json); - - - FileSystemWatcher_CN = new FileSystemWatcher - { - Path = Path.GetDirectoryName(cnJsonPath)!, - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size, - Filter = Path.GetFileName(cnJsonPath), - EnableRaisingEvents = true, - - }; - FileSystemWatcher_CN.Changed += (sender, e) => LoadJsonFile(StaticData.Folder.Resources + "\\" + StaticData.Zh_CN_Json); - } - private static void LoadJsonFile(string filePath) + public static async Task BatchAddJsonKeyValueAsync(List list) { - Console.WriteLine("刷新json内存数据"); - IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile(filePath, false, false); + await StoreInfoToFileAsync(list); + } - IConfigurationRoot enConfiguration = builder.Build(); + public static async Task AddOrUpdateJsonKeyValueAsync(IRCGlobalInfoDTO info) + { + var list = new List() { info }; - foreach (IConfigurationSection section in enConfiguration.GetChildren()) + await StoreInfoToFileAsync(list); + + } + + + public static async Task StoreInfoToFileAsync(List list) + { + foreach (var filePath in new string[] { USJsonPath, CNJsonPath }) { + var json = await File.ReadAllTextAsync(filePath); + + JObject jsonObject = JObject.Parse(json, new JsonLoadSettings() { CommentHandling = CommentHandling.Load }); + if (filePath.Contains(StaticData.En_US_Json)) { - StaticData.En_US_Dic[section.Key] = section.Value; + foreach (var tojsonItem in list) + { + jsonObject[tojsonItem.Code] = tojsonItem.Value; + //日志记录该信息方便自己人看, 返回给客户的是配置的 + StaticData.Localizer_Dev_Dic[tojsonItem.Code] = tojsonItem.Description; + } } else { - StaticData.Zh_CN_Dic[section.Key] = section.Value; + foreach (var tojsonItem in list) + { + jsonObject[tojsonItem.Code] = tojsonItem.ValueCN; + + } } + + await File.WriteAllTextAsync(filePath, jsonObject.ToString()); } } + #region 监测Json文件变更 实时刷新数据 废弃 + //FileSystemWatcher_US = new FileSystemWatcher + //{ + // Path = Path.GetDirectoryName(USJsonPath)!, + // NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size, + // Filter = Path.GetFileName(USJsonPath), + // EnableRaisingEvents = true, + + //}; + //// 添加文件更改事件的处理程序 + //FileSystemWatcher_US.Changed += (sender, e) => LoadJsonFile(StaticData.Folder.Resources + "\\" + StaticData.En_US_Json); + + + //FileSystemWatcher_CN = new FileSystemWatcher + //{ + // Path = Path.GetDirectoryName(CNJsonPath)!, + // NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size, + // Filter = Path.GetFileName(CNJsonPath), + // EnableRaisingEvents = true, + + //}; + //FileSystemWatcher_CN.Changed += (sender, e) => LoadJsonFile(StaticData.Folder.Resources + "\\" + StaticData.Zh_CN_Json); + + + //private static void LoadJsonFile(string filePath) + //{ + // Console.WriteLine("刷新json内存数据"); + // IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile(filePath, false, false); + + // IConfigurationRoot enConfiguration = builder.Build(); + + // foreach (IConfigurationSection section in enConfiguration.GetChildren()) + // { + // if (filePath.Contains(StaticData.En_US_Json)) + // { + // StaticData.En_US_Dic[section.Key] = section.Value; + + // } + // else + // { + // StaticData.Zh_CN_Dic[section.Key] = section.Value; + // } + // } + //} + + #endregion + + diff --git a/IRaCIS.Core.Application/Service/Common/InternationalizationService.cs b/IRaCIS.Core.Application/Service/Common/InternationalizationService.cs index f3f03ec80..5adee87e1 100644 --- a/IRaCIS.Core.Application/Service/Common/InternationalizationService.cs +++ b/IRaCIS.Core.Application/Service/Common/InternationalizationService.cs @@ -175,7 +175,7 @@ namespace IRaCIS.Core.Application.Service if (addOrEditInternationalization.InternationalizationType == 1) { - await InternationalizationHelper.AddOrUpdateJsonKeyValueAsync(entity.Code, addOrEditInternationalization.Value, addOrEditInternationalization.ValueCN, addOrEditInternationalization.Description); + await InternationalizationHelper.AddOrUpdateJsonKeyValueAsync(_mapper.Map(addOrEditInternationalization)); } else { diff --git a/IRaCIS.Core.Application/Service/Common/_MapConfig.cs b/IRaCIS.Core.Application/Service/Common/_MapConfig.cs index d37f359fd..607f64bfc 100644 --- a/IRaCIS.Core.Application/Service/Common/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/Common/_MapConfig.cs @@ -1,6 +1,7 @@ using AutoMapper; using IRaCIS.Application.Contracts; using IRaCIS.Core.Application.Contracts; +using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.ViewModel; namespace IRaCIS.Core.Application.Service @@ -70,7 +71,10 @@ namespace IRaCIS.Core.Application.Service CreateMap().ReverseMap(); + CreateMap(); + CreateMap(); + CreateMap(); diff --git a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs index b651aa78a..e437bdf91 100644 --- a/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs +++ b/IRaCIS.Core.Infrastructure/_IRaCIS/_Config/_StaticData.cs @@ -8,11 +8,6 @@ namespace IRaCIS.Core.Domain.Share; public static class StaticData { - - public static Dictionary En_US_Dic = new Dictionary(); - - public static Dictionary Zh_CN_Dic = new Dictionary(); - public static Dictionary Localizer_Dev_Dic = new Dictionary(); @@ -29,24 +24,33 @@ public static class StaticData public static readonly string en_US_bookMark = "en_us"; } - /// - /// 获取国际化 - /// - /// - /// - public static string I18n(string key, params object?[] args) - { - var isEn_US = System.Globalization.CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + #region 国际化废弃 + + //public static Dictionary En_US_Dic = new Dictionary(); + + //public static Dictionary Zh_CN_Dic = new Dictionary(); + ///// + ///// 获取国际化 + ///// + ///// + ///// + //public static string I18n(string key, params object?[] args) + //{ + // var isEn_US = System.Globalization.CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + + // if (isEn_US) + // { + // return En_US_Dic[key]; + // } + // else + // { + // return Zh_CN_Dic[key]; + // } + //} + + + #endregion - if (isEn_US) - { - return En_US_Dic[key]; - } - else - { - return Zh_CN_Dic[key]; - } - } #endregion From 26652160df62cffa2271b95fef27cfbb3877f107 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 17 Oct 2024 09:01:52 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E8=AE=A1=E5=88=92=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A41?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 8 -- .../BackGroundJob/IRaCISCHangfireJob.cs | 50 ++++----- .../Helper/HangfireJobHelper.cs | 4 +- .../IRaCIS.Core.Application.xml | 67 ++++++------ .../NeedVerify/AddSubjectTriggerConsumer.cs | 101 ------------------ .../Consumer/{NeedVerify => }/TestConsumer.cs | 1 + .../Recurring/RecurringEmailConsumer.cs | 56 ++++++++++ .../Service/Document/EmailSendService.cs | 4 +- IRaCIS.Core.Application/TestService.cs | 2 + .../Common/EmailScenarioEnum.cs | 6 +- 10 files changed, 123 insertions(+), 176 deletions(-) delete mode 100644 IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs rename IRaCIS.Core.Application/MassTransit/Consumer/{NeedVerify => }/TestConsumer.cs (99%) create mode 100644 IRaCIS.Core.Application/MassTransit/Recurring/RecurringEmailConsumer.cs diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 2e99be6b9..4aabde1ba 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -63,14 +63,6 @@ builder.Configuration.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), "a builder.Host.UseSerilog(); -#region Autofac 废弃 -//builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) -// .ConfigureContainer(containerBuilder => -// { -// containerBuilder.RegisterModule(); -// }).UseWindowsService(); -#endregion - #endregion diff --git a/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs b/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs index 29fbf83ee..18b02bbf5 100644 --- a/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs +++ b/IRaCIS.Core.Application/BackGroundJob/IRaCISCHangfireJob.cs @@ -1,7 +1,10 @@ using Hangfire; using Hangfire.Storage; using IRaCIS.Core.Application.Helper; +using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Share; +using MassTransit; +using MassTransit.Scheduling; using Microsoft.Extensions.Logging; namespace IRaCIS.Core.Application.Service.BackGroundJob @@ -14,7 +17,8 @@ namespace IRaCIS.Core.Application.Service.BackGroundJob } public class IRaCISCHangfireJob(ILogger _logger, IRepository _internationalizationRepository, - IRepository _trialEmailNoticeConfigRepository + IRepository _trialEmailNoticeConfigRepository, + IMessageScheduler _IMessageScheduler ) : IIRaCISHangfireJob { public static string JsonFileFolder = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources); @@ -49,15 +53,6 @@ namespace IRaCIS.Core.Application.Service.BackGroundJob 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 }) @@ -68,24 +63,29 @@ namespace IRaCIS.Core.Application.Service.BackGroundJob //利用主键作为任务Id var jobId = $"{task.TrialId}_{task.Id}"; + switch (task.BusinessScenarioEnum) + { + + case EmailBusinessScenario.QCTask: + + break; + + case EmailBusinessScenario.CRCToQCQuestion: + + break; + case EmailBusinessScenario.QCToCRCImageQuestion: + break; + + default: + break; + } + + + + 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); - // } - - } diff --git a/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs b/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs index 7b4d669c3..f27c6c1f9 100644 --- a/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs +++ b/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs @@ -64,10 +64,10 @@ namespace IRaCIS.Core.Application.Helper case EmailBusinessScenario.QCTask: HangfireJobHelper.AddOrUpdateCronJob(jobId, t => t.SendTrialImageQCTaskEmailAsync(trialId), emailCron); break; - case EmailBusinessScenario.QCQuestion: + case EmailBusinessScenario.CRCToQCQuestion: HangfireJobHelper.AddOrUpdateCronJob(jobId, t => t.SendTrialQCQuestionEmailAsync(trialId), emailCron); break; - case EmailBusinessScenario.ImageQuestion: + case EmailBusinessScenario.QCToCRCImageQuestion: HangfireJobHelper.AddOrUpdateCronJob(jobId, t => t.SendTrialImageQuestionAsync(trialId), emailCron); break; diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 0bfa238ee..21ebebc56 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -12897,42 +12897,6 @@ 构造函数注入 - - - 添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致 - 因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务 - - - - - - - - - 添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致 - 因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务 - - - - - - - - - meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码 - publish 请求流不会先到消费者,发布后,直接执行后续代码 - - - - - - - meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码 - publish 请求流不会先到消费者,发布后,直接执行后续代码 - - - - 加急的医学反馈任务 通知MIM @@ -13021,11 +12985,42 @@ 通知PM 进行一致性核查 + + + meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码 + publish 请求流不会先到消费者,发布后,直接执行后续代码 + + + + + + + meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码 + publish 请求流不会先到消费者,发布后,直接执行后续代码 + + + + 参考链接:https://github.com/MassTransit/MassTransit/discussions/2498 + + + QC 影像质疑待处理 + + + + + CRC 影像质疑 + + + + + 影像质控 + + TaskAllocationRuleView 列表视图模型 diff --git a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs b/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs deleted file mode 100644 index f9875948e..000000000 --- a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/AddSubjectTriggerConsumer.cs +++ /dev/null @@ -1,101 +0,0 @@ - - -using AutoMapper; -using IRaCIS.Core.Domain; -using MassTransit; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; - -namespace IRaCIS.Core.Application.MassTransit.Consumer; - - - - -/// -/// 添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致 -/// 因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务 -/// -/// -/// -/// -/// -public class AddSubjectTriggerConsumer(IRepository _subjectVisitRepository, - - IRepository _visitStageRepository, - IRepository _trialRepository, - IMapper _mapper) : IConsumer -{ - public async Task Consume(ConsumeContext context) - { - var addSubjectEvent = context.Message; - - - { - Console.WriteLine(_visitStageRepository._dbContext.GetHashCode()); - - Console.WriteLine("两个 DbContext 不是同一个实例"); - } - - - //添加受试者的时候,获取访视计划列表,添加到受试者访视表。 - var visitPlanList = await _visitStageRepository.Where(t => t.TrialId == addSubjectEvent.TrialId && t.IsConfirmed).ToListAsync(); - - var svList = _mapper.Map>(visitPlanList); - - var IsEnrollementQualificationConfirm = await _trialRepository.Where(t => t.Id == addSubjectEvent.TrialId).Select(u => u.IsEnrollementQualificationConfirm).FirstOrDefaultAsync(); - - svList.ForEach(t => - { - t.SubjectId = addSubjectEvent.SubjectId; - t.TrialId = addSubjectEvent.TrialId; - t.TrialSiteId = addSubjectEvent.TrialSiteId; - t.IsEnrollmentConfirm = t.IsBaseLine ? IsEnrollementQualificationConfirm : false; - t.Id = NewId.NextGuid(); - - }); - - await _subjectVisitRepository.AddRangeAsync(svList); - } -} - -public class AddSubjectTriggerConsumer2(IRepository _subjectVisitRepository, - - IRepository _visitStageRepository, - IRepository _trialRepository, - IMapper _mapper) : IConsumer -{ - public async Task Consume(ConsumeContext context) - { - var addSubjectEvent = context.Message; - - - { - Console.WriteLine(_visitStageRepository._dbContext.GetHashCode()); - - Console.WriteLine("两个 DbContext 不是同一个实例"); - } - - - //添加受试者的时候,获取访视计划列表,添加到受试者访视表。 - var visitPlanList = await _visitStageRepository.Where(t => t.TrialId == addSubjectEvent.TrialId && t.IsConfirmed).ToListAsync(); - - var svList = _mapper.Map>(visitPlanList); - - var IsEnrollementQualificationConfirm = await _trialRepository.Where(t => t.Id == addSubjectEvent.TrialId).Select(u => u.IsEnrollementQualificationConfirm).FirstOrDefaultAsync(); - - svList.ForEach(t => - { - t.SubjectId = addSubjectEvent.SubjectId; - t.TrialId = addSubjectEvent.TrialId; - t.TrialSiteId = addSubjectEvent.TrialSiteId; - t.IsEnrollmentConfirm = t.IsBaseLine ? IsEnrollementQualificationConfirm : false; - t.Id = NewId.NextGuid(); - - }); - - await _subjectVisitRepository.AddRangeAsync(svList); - } -} - - diff --git a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/TestConsumer.cs b/IRaCIS.Core.Application/MassTransit/Consumer/TestConsumer.cs similarity index 99% rename from IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/TestConsumer.cs rename to IRaCIS.Core.Application/MassTransit/Consumer/TestConsumer.cs index 07d313afa..0dbef2b9d 100644 --- a/IRaCIS.Core.Application/MassTransit/Consumer/NeedVerify/TestConsumer.cs +++ b/IRaCIS.Core.Application/MassTransit/Consumer/TestConsumer.cs @@ -54,6 +54,7 @@ public class TestMasstransitService : BaseService { var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + Console.WriteLine(_testLengthRepository._dbContext.GetHashCode()); //IScopedMediator 上下文一致, IMediator上下文不一致 diff --git a/IRaCIS.Core.Application/MassTransit/Recurring/RecurringEmailConsumer.cs b/IRaCIS.Core.Application/MassTransit/Recurring/RecurringEmailConsumer.cs new file mode 100644 index 000000000..fb35a334a --- /dev/null +++ b/IRaCIS.Core.Application/MassTransit/Recurring/RecurringEmailConsumer.cs @@ -0,0 +1,56 @@ +using MassTransit; +using MassTransit.Scheduling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.MassTransit.Consumer; +public abstract class IRCRecurringSchedule : + RecurringSchedule +{ + protected IRCRecurringSchedule() + { + ScheduleGroup = GetType().Name; + + TimeZoneId = TimeZoneInfo.Local.Id; + StartTime = DateTime.Now; + } + + public MissedEventPolicy MisfirePolicy { get; protected set; } + public string TimeZoneId { get; protected set; } + public DateTimeOffset StartTime { get; protected set; } + public DateTimeOffset? EndTime { get; protected set; } + public string ScheduleId { get; private set; } + public string ScheduleGroup { get; private set; } + public string CronExpression { get; protected set; } + public string Description { get; protected set; } +} + +/// +/// QC 影像质疑待处理 +/// +public class QCImageQuestionSchedule : IRCRecurringSchedule +{ + +} + +/// +/// CRC 影像质疑 +/// +public class CRCImageQuestionSchedule : IRCRecurringSchedule +{ + +} + +/// +/// 影像质控 +/// +public class ImageQCSchedule : IRCRecurringSchedule +{ + +} + + + diff --git a/IRaCIS.Core.Application/Service/Document/EmailSendService.cs b/IRaCIS.Core.Application/Service/Document/EmailSendService.cs index d64befeff..16aa1eb8d 100644 --- a/IRaCIS.Core.Application/Service/Document/EmailSendService.cs +++ b/IRaCIS.Core.Application/Service/Document/EmailSendService.cs @@ -203,7 +203,7 @@ namespace IRaCIS.Core.Application.Service return (topicStr, htmlBodyStr, false, userId); }; - await SendTrialEmailAsync(trialId, EmailBusinessScenario.QCQuestion, topicAndHtmlFunc); + await SendTrialEmailAsync(trialId, EmailBusinessScenario.CRCToQCQuestion, topicAndHtmlFunc); } } } @@ -253,7 +253,7 @@ namespace IRaCIS.Core.Application.Service return (topicStr, htmlBodyStr, isEn_us, userId); }; - await SendTrialEmailAsync(trialId, EmailBusinessScenario.ImageQuestion, topicAndHtmlFunc); + await SendTrialEmailAsync(trialId, EmailBusinessScenario.QCToCRCImageQuestion, topicAndHtmlFunc); } } } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index e85e65479..f3955893b 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -30,6 +30,7 @@ using System.Runtime.InteropServices; using System.Text; + namespace IRaCIS.Core.Application.Service { /// @@ -58,6 +59,7 @@ namespace IRaCIS.Core.Application.Service [AllowAnonymous] public IResponseOutput GetTest() { + //throw new BusinessValidationFailedException("手动抛出的异常"); return ResponseOutput.Ok(_userInfo.IP); diff --git a/IRaCIS.Core.Domain.Share/Common/EmailScenarioEnum.cs b/IRaCIS.Core.Domain.Share/Common/EmailScenarioEnum.cs index 64594296b..1caeec8d2 100644 --- a/IRaCIS.Core.Domain.Share/Common/EmailScenarioEnum.cs +++ b/IRaCIS.Core.Domain.Share/Common/EmailScenarioEnum.cs @@ -46,11 +46,13 @@ namespace IRaCIS.Core.Domain.Share //Reviewer=4, + //QC影像质控任务 QCTask = 5, - QCQuestion = 6, - ImageQuestion = 7, + CRCToQCQuestion = 6, + + QCToCRCImageQuestion = 7, From 41e8164c2e8a35ec1c59bc383d0ff1d30c2fafb0 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 17 Oct 2024 09:23:28 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Helper/InternationalizationHelper.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs index 90c2c9120..767c0a66e 100644 --- a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs +++ b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs @@ -27,15 +27,15 @@ public static class InternationalizationHelper public static string USJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); public static string CNJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - static InternationalizationHelper() - { - if (!Directory.Exists(JsonFileFolder) || - !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))|| - !File.Exists(USJsonPath) || !File.Exists(CNJsonPath)) - { - throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound")); - } - } + //static InternationalizationHelper() + //{ + // if (!Directory.Exists(JsonFileFolder) || + // !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))|| + // !File.Exists(USJsonPath) || !File.Exists(CNJsonPath)) + // { + // throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound")); + // } + //} From 8a3eb61a34b86cb42e37bb56b99aebc0bf015789 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 17 Oct 2024 10:03:47 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 8 +++++--- .../Helper/InternationalizationHelper.cs | 18 +++++++++--------- .../IRaCIS.Core.Application.csproj | 4 ++++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 4aabde1ba..6d2307421 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -248,14 +248,16 @@ app.MapMasaMinimalAPIs(); // Serilog SerilogExtension.AddSerilogSetup(enviromentName, app.Services); -var hangfireJobService = app.Services.GetRequiredService(); - -await hangfireJobService.InitHangfireJobTaskAsync(); //设置国际化I18n var localizer = app.Services.GetRequiredService(); I18n.SetLocalizer(localizer); +var hangfireJobService = app.Services.GetRequiredService(); + +await hangfireJobService.InitHangfireJobTaskAsync(); + + try { #region 运行环境 部署平台 diff --git a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs index 767c0a66e..05bb7c97f 100644 --- a/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs +++ b/IRaCIS.Core.Application/Helper/InternationalizationHelper.cs @@ -27,15 +27,15 @@ public static class InternationalizationHelper public static string USJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json); public static string CNJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json); - //static InternationalizationHelper() - //{ - // if (!Directory.Exists(JsonFileFolder) || - // !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))|| - // !File.Exists(USJsonPath) || !File.Exists(CNJsonPath)) - // { - // throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound")); - // } - //} + static InternationalizationHelper() + { + if (!Directory.Exists(JsonFileFolder) || + !Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase)) || + !File.Exists(USJsonPath) || !File.Exists(CNJsonPath)) + { + throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound")); + } + } diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj index eb12535aa..9c039cd04 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj @@ -27,6 +27,7 @@ + @@ -40,6 +41,9 @@ true PreserveNewest + + Always +