Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8

IRC_NewDev
he 2024-10-17 10:14:52 +08:00
commit 5745161142
42 changed files with 672 additions and 620 deletions

View File

@ -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();

View File

@ -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<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
context.HttpContext.Request.EnableBuffering();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
[DisableFormValueModelBinding]
public abstract class UploadBaseController : ControllerBase
{

View File

@ -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;
@ -62,14 +63,6 @@ builder.Configuration.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), "a
builder.Host.UseSerilog();
#region Autofac 废弃
//builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
// .ConfigureContainer<ContainerBuilder>(containerBuilder =>
// {
// containerBuilder.RegisterModule<AutofacModuleSetup>();
// }).UseWindowsService();
#endregion
#endregion
@ -79,8 +72,8 @@ var _configuration = builder.Configuration;
//手动注册服务
builder.Services.ConfigureServices(_configuration);
//异常处理
//builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//minimal api 异常处理
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//builder.Services.AddProblemDetails();
//健康检查
@ -91,7 +84,6 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
//options.Filters.Add<LogActionFilter>();
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
@ -148,7 +140,11 @@ builder.Services.AddMasaMinimalAPIs(options =>
options.DeletePrefixes = new List<string> { "Delete", "Remove" };
options.RouteHandlerBuilder= t=> {
t.RequireAuthorization().AddEndpointFilter<UnifiedApiResultEndpointFilter>().WithGroupName("Institution");
t.RequireAuthorization()
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
.AddEndpointFilter<ModelValidationEndpointFilter>()
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
.WithGroupName("Institution");
};
options.DisableTrimMethodPrefix = true; //禁用去除方法前缀
options.DisableAutoMapRoute = false;//可通过配置true禁用全局自动路由映射或者删除此配置以启用全局自动路由映射
@ -186,28 +182,8 @@ app.UseStatusCodePages(async context =>
});
//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);
//}
// });
//});
//app.UseExceptionHandler();
app.UseExceptionHandler(o => { });
#region 暂时废弃
@ -272,10 +248,16 @@ app.MapMasaMinimalAPIs();
// Serilog
SerilogExtension.AddSerilogSetup(enviromentName, app.Services);
//设置国际化I18n
var localizer = app.Services.GetRequiredService<IStringLocalizer>();
I18n.SetLocalizer(localizer);
var hangfireJobService = app.Services.GetRequiredService<IIRaCISHangfireJob>();
await hangfireJobService.InitHangfireJobTaskAsync();
try
{
#region 运行环境 部署平台

View File

@ -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
@ -9,15 +12,13 @@ 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
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IMessageScheduler _IMessageScheduler
) : IIRaCISHangfireJob
{
public static string JsonFileFolder = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources);
@ -29,43 +30,29 @@ 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<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 })
@ -76,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);
// }
}

View File

@ -1,43 +0,0 @@
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

@ -13,24 +13,10 @@ namespace IRaCIS.Core.Application.Filter;
public class LimitUserRequestAuthorization : IAsyncAuthorizationFilter
public class LimitUserRequestAuthorization(
IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer,
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig) : IAsyncAuthorizationFilter
{
public IStringLocalizer _localizer { get; set; }
private readonly IFusionCache _fusionCache;
private readonly IUserInfo _userInfo;
private readonly IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig;
public LimitUserRequestAuthorization(IFusionCache fusionCache, IUserInfo userInfo, IStringLocalizer localizer, IOptionsMonitor<ServiceVerifyConfigOption> verifyConfig)
{
_fusionCache = fusionCache;
_userInfo = userInfo;
_verifyConfig = verifyConfig;
_localizer = localizer;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{

View File

@ -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)
{

View File

@ -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<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);
}
else
{
//继续
}
}
}

View File

@ -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();

View File

@ -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;
/// <summary>
/// minimal api 生效,但是传统控制器,没生效
/// 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0
/// </summary>
public class GlobalExceptionHandler(IStringLocalizer _localizer, ILogger<GlobalExceptionHandler> _logger) : IExceptionHandler
{
public ValueTask<bool> 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.Localizer_Dev_Dic.ContainsKey(error!.LocalizedKey))
{
info = $"[{error!.LocalizedKey}]:{StaticData.Localizer_Dev_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);
}
}

View File

@ -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<ServiceVerifyConfigOption> _verifyConfig) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var minutes = _verifyConfig.CurrentValue.AutoLoginOutMinutes;
if (_verifyConfig.CurrentValue.OpenLoginLimit)
{
// 如果此请求允许匿名访问,不进行处理
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != 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<string>(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<string>(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

View File

@ -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<object?> 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<ValidationResult>();
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);
}
}

View File

@ -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<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
throw new NotImplementedException();
}
}

View File

@ -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<UnifiedApiResultEndpointFilter> _logger) : IEndpointFilter
{
private readonly ILogger _logger;
public UnifiedApiResultEndpointFilter(ILogger<UnifiedApiResultFilter> logger)
{
_logger = logger;
}
public async ValueTask<object?> 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;
}

View File

@ -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<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
context.HttpContext.Request.EnableBuffering();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
#endregion

View File

@ -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<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;
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
{
//继续
}
}
}

View File

@ -135,7 +135,7 @@ public static class FileStoreHelper
if (doc == null)
{
//---数据库没有找到对应的数据模板文件,请联系系统运维人员。
throw new BusinessValidationFailedException(StaticData.International("FileStore_TemplateFileNotFound"));
throw new BusinessValidationFailedException(I18n.T("FileStore_TemplateFileNotFound"));
}
var filePath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, doc.Path);
@ -143,7 +143,7 @@ public static class FileStoreHelper
if (!System.IO.File.Exists(filePath))
{
//---数据模板文件存储路径上未找对应文件,请联系系统运维人员。
throw new BusinessValidationFailedException(StaticData.International("FileStore_TemplateFileStoragePathInvalid"));
throw new BusinessValidationFailedException(I18n.T("FileStore_TemplateFileStoragePathInvalid"));
}
return (filePath, isEn_US ? doc.Name.Trim('/') : doc.NameCN.Trim('/'));
@ -269,7 +269,7 @@ public static class FileStoreHelper
{
//---解析Json文件配置出现问题
throw new BusinessValidationFailedException(StaticData.International("SysMon_JsonConfig") + e.Message);
throw new BusinessValidationFailedException(I18n.T("SysMon_JsonConfig") + e.Message);
}
//默认存储的路径

View File

@ -64,10 +64,10 @@ namespace IRaCIS.Core.Application.Helper
case EmailBusinessScenario.QCTask:
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialImageQCTaskEmailAsync(trialId), emailCron);
break;
case EmailBusinessScenario.QCQuestion:
case EmailBusinessScenario.CRCToQCQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialQCQuestionEmailAsync(trialId), emailCron);
break;
case EmailBusinessScenario.ImageQuestion:
case EmailBusinessScenario.QCToCRCImageQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialImageQuestionAsync(trialId), emailCron);
break;

View File

@ -4,201 +4,134 @@ 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)))
!Directory.GetFiles(JsonFileFolder).Any(filePath => Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase)) ||
!File.Exists(USJsonPath) || !File.Exists(CNJsonPath))
{
throw new BusinessValidationFailedException("国际化Json文件目录有误");
throw new BusinessValidationFailedException(I18n.T("IRaCISCHangfireJob_FileNotFound"));
}
}
public static async Task BatchAddJsonKeyValueAsync(List<BatchAddInternationalizationDto> batchAddDtos)
public static async Task BatchAddJsonKeyValueAsync(List<IRCGlobalInfoDTO> list)
{
VerifyFolder();
await StoreInfoToFileAsync(list);
}
var usJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json);
var cnJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json);
public static async Task AddOrUpdateJsonKeyValueAsync(IRCGlobalInfoDTO info)
{
var list = new List<IRCGlobalInfoDTO>() { info };
await StoreInfoToFileAsync(list);
}
//更新json 文件 同时更新内存缓存的数据
foreach (var filePath in new string[] { usJsonPath, cnJsonPath })
public static async Task StoreInfoToFileAsync(List<IRCGlobalInfoDTO> 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))
{
foreach (var item in batchAddDtos)
foreach (var tojsonItem in list)
{
jsonObject[item.Code] = item.Value;
StaticData.En_US_Dic[item.Code] = item.Value;
jsonObject[tojsonItem.Code] = tojsonItem.Value;
//日志记录该信息方便自己人看, 返回给客户的是配置的
StaticData.Log_Locoalize_Dic[item.Code] = item.Description;
StaticData.Localizer_Dev_Dic[tojsonItem.Code] = tojsonItem.Description;
}
}
else
{
foreach (var item in batchAddDtos)
foreach (var tojsonItem in list)
{
jsonObject[item.Code] = item.Value;
jsonObject[tojsonItem.Code] = tojsonItem.ValueCN;
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);
#region 监测Json文件变更 实时刷新数据 废弃
//FileSystemWatcher_US = new FileSystemWatcher
//{
// Path = Path.GetDirectoryName(USJsonPath)!,
// NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
// Filter = Path.GetFileName(USJsonPath),
// EnableRaisingEvents = true,
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.Log_Locoalize_Dic[key] = description;
}
else
{
jsonObject[key] = valueCN;
StaticData.Zh_CN_Dic[key] = valueCN;
}
//};
//// 添加文件更改事件的处理程序
//FileSystemWatcher_US.Changed += (sender, e) => LoadJsonFile(StaticData.Folder.Resources + "\\" + StaticData.En_US_Json);
await File.WriteAllTextAsync(filePath, jsonObject.ToString());
//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);
public static async Task InitInternationlizationDataAndWatchJsonFileAsync(IRepository<Internationalization> _internationalizationRepository)
{
//查询数据库的数据
var toJsonList = await _internationalizationRepository.Where(t => t.InternationalizationType == 1).Select(t => new
{
t.Code,
t.Value,
t.ValueCN,
t.Description
}).ToListAsync();
//private static void LoadJsonFile(string filePath)
//{
// Console.WriteLine("刷新json内存数据");
// IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile(filePath, false, false);
//组织成json 文件
// IConfigurationRoot enConfiguration = builder.Build();
var usJsonPath = Path.Combine(JsonFileFolder, StaticData.En_US_Json);
var cnJsonPath = Path.Combine(JsonFileFolder, StaticData.Zh_CN_Json);
// 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;
// }
// }
//}
//本地静态文件国际化需要
foreach (var tojsonItem in toJsonList)
{
StaticData.En_US_Dic[tojsonItem.Code] = tojsonItem.Value;
StaticData.Zh_CN_Dic[tojsonItem.Code] = tojsonItem.ValueCN;
//日志记录该信息方便自己人看, 返回给客户的是配置的
StaticData.Log_Locoalize_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))
{
throw new BusinessValidationFailedException(StaticData.International("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)
{
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

View File

@ -42,7 +42,7 @@ public static class SendEmailHelper
{
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
throw new Exception(StaticData.International("SendEmail_SendFail"), new Exception(ex.Message));
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
@ -82,7 +82,7 @@ public static class SendEmailHelper
if (sMTPEmailConfig.ToMailAddressList.Count == 0)
{
//---没有收件人
throw new ArgumentException(StaticData.International("SendEmail_NoRecipient"));
throw new ArgumentException(I18n.T("SendEmail_NoRecipient"));
}
else
{

View File

@ -27,6 +27,7 @@
<ItemGroup>
<None Remove="IRaCIS.Core.Application.xml" />
<None Remove="Resources\en-US.json" />
<None Remove="Resources\zh-CN.json" />
<None Remove="Resources\zh_ch.json" />
<None Remove="Service\Allocation\TaskConsistentRuleService.cs~RF1603d47.TMP" />
<None Remove="Service\Reading\ReadingImageTask\ReadingImageTaskService.cs~RF2f9323.TMP" />
@ -40,6 +41,9 @@
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\zh-CN.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View File

@ -12747,7 +12747,14 @@
</member>
<member name="T:IRaCIS.Core.Application.BusinessFilter.GlobalExceptionHandler">
<summary>
不生效,不知道为啥
minimal api 生效,但是传统控制器,没生效
参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0
</summary>
</member>
<member name="M:IRaCIS.Core.Application.BusinessFilter.GlobalExceptionHandler.#ctor(Microsoft.Extensions.Localization.IStringLocalizer,Microsoft.Extensions.Logging.ILogger{IRaCIS.Core.Application.BusinessFilter.GlobalExceptionHandler})">
<summary>
minimal api 生效,但是传统控制器,没生效
参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0
</summary>
</member>
<member name="T:IRaCIS.Core.Application.Filter.TrialResourceFilter">
@ -12890,42 +12897,6 @@
构造函数注入
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.AddSubjectTriggerConsumer">
<summary>
添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致
因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务
</summary>
<param name="_subjectVisitRepository"></param>
<param name="_visitStageRepository"></param>
<param name="_trialRepository"></param>
<param name="_mapper"></param>
</member>
<member name="M:IRaCIS.Core.Application.MassTransit.Consumer.AddSubjectTriggerConsumer.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitStage},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},AutoMapper.IMapper)">
<summary>
添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致
因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务
</summary>
<param name="_subjectVisitRepository"></param>
<param name="_visitStageRepository"></param>
<param name="_trialRepository"></param>
<param name="_mapper"></param>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.MasstransitTestConsumer">
<summary>
meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码
publish 请求流不会先到消费者,发布后,直接执行后续代码
</summary>
<param name="_userRepository"></param>
</member>
<member name="M:IRaCIS.Core.Application.MassTransit.Consumer.MasstransitTestConsumer.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.User})">
<summary>
meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码
publish 请求流不会先到消费者,发布后,直接执行后续代码
</summary>
<param name="_userRepository"></param>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.UrgentMedicalReviewAddedEventConsumer">
<summary>
加急的医学反馈任务 通知MIM
@ -13014,11 +12985,42 @@
通知PM 进行一致性核查
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.MasstransitTestConsumer">
<summary>
meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码
publish 请求流不会先到消费者,发布后,直接执行后续代码
</summary>
<param name="_userRepository"></param>
</member>
<member name="M:IRaCIS.Core.Application.MassTransit.Consumer.MasstransitTestConsumer.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.User})">
<summary>
meditor send 的时候,请求流会先到消费者,返回后才会执行后续代码
publish 请求流不会先到消费者,发布后,直接执行后续代码
</summary>
<param name="_userRepository"></param>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.MediatorHttpContextScopeFilterExtensions">
<summary>
参考链接https://github.com/MassTransit/MassTransit/discussions/2498
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.QCImageQuestionSchedule">
<summary>
QC 影像质疑待处理
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.CRCImageQuestionSchedule">
<summary>
CRC 影像质疑
</summary>
</member>
<member name="T:IRaCIS.Core.Application.MassTransit.Consumer.ImageQCSchedule">
<summary>
影像质控
</summary>
</member>
<member name="T:IRaCIS.Core.Application.ViewModel.TaskAllocationRuleView">
<summary> TaskAllocationRuleView 列表视图模型 </summary>
</member>

View File

@ -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;
/// <summary>
/// 添加Subject 触发添加访视 不能代替 Trigger,稽查BatchId 不一致
/// 因为消费者这里的数据库上下文 和消息发送者上下文不是同一个,相当于两个独立的事务
/// </summary>
/// <param name="_subjectVisitRepository"></param>
/// <param name="_visitStageRepository"></param>
/// <param name="_trialRepository"></param>
/// <param name="_mapper"></param>
public class AddSubjectTriggerConsumer(IRepository<SubjectVisit> _subjectVisitRepository,
IRepository<VisitStage> _visitStageRepository,
IRepository<Trial> _trialRepository,
IMapper _mapper) : IConsumer<AddSubjectTriggerCommand>
{
public async Task Consume(ConsumeContext<AddSubjectTriggerCommand> 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<List<SubjectVisit>>(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<SubjectVisit> _subjectVisitRepository,
IRepository<VisitStage> _visitStageRepository,
IRepository<Trial> _trialRepository,
IMapper _mapper) : IConsumer<AddSubjectTriggerCommand2>
{
public async Task Consume(ConsumeContext<AddSubjectTriggerCommand2> 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<List<SubjectVisit>>(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);
}
}

View File

@ -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上下文不一致

View File

@ -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; }
}
/// <summary>
/// QC 影像质疑待处理
/// </summary>
public class QCImageQuestionSchedule : IRCRecurringSchedule
{
}
/// <summary>
/// CRC 影像质疑
/// </summary>
public class CRCImageQuestionSchedule : IRCRecurringSchedule
{
}
/// <summary>
/// 影像质控
/// </summary>
public class ImageQCSchedule : IRCRecurringSchedule
{
}

View File

@ -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<IRCGlobalInfoDTO>(addOrEditInternationalization));
}
else
{

View File

@ -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<Internationalization, BatchInternationalizationDto>().ReverseMap();
CreateMap<Internationalization, IRCGlobalInfoDTO>();
CreateMap<EventStoreRecord, EventStoreRecordView>();
CreateMap<BatchAddInternationalizationDto, InternationalizationAddOrEdit>();

View File

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

View File

@ -397,7 +397,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
if (_fusionCache.GetOrDefault<Guid>(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
{

View File

@ -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<Guid>(CacheKeys.TrialStudyUidDBLock(incommand.TrialId, incommand.Study.StudyInstanceUid)) != Guid.Empty)
{
//---当前已有人正在上传和归档该检查!
return ResponseOutput.NotOk(StaticData.International("UploadDownLoad_ArchiveInProgress"));
return ResponseOutput.NotOk(I18n.T("UploadDownLoad_ArchiveInProgress"));
}
else
{

View File

@ -28,7 +28,7 @@ using NPOI.XWPF.UserModel;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Tea;
namespace IRaCIS.Core.Application.Service
@ -56,10 +56,25 @@ namespace IRaCIS.Core.Application.Service
return Task.FromResult(list);
}
[AllowAnonymous]
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"));
}
}
@ -167,6 +182,8 @@ namespace IRaCIS.Core.Application.Service
public async Task<IResponseOutput> TestJson()
{
throw new BusinessValidationFailedException("传统控制器异常");
var model1 = new TestModel() { TestId = NewId.NextSequentialGuid(), TestName = null };
var model2 = new TestModel2() { TestId = NewId.NextSequentialGuid(), TestName = "test2" };

View File

@ -46,11 +46,13 @@ namespace IRaCIS.Core.Domain.Share
//Reviewer=4,
//QC影像质控任务
QCTask = 5,
QCQuestion = 6,
ImageQuestion = 7,
CRCToQCQuestion = 6,
QCToCRCImageQuestion = 7,

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
/// <summary>
/// 设置是使用哪个仓储 默认不跟踪
@ -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

View File

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

View File

@ -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];
}
}

View File

@ -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<string>().NotOk(msg, code: code, localizedInfo: info);

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
@ -7,12 +8,7 @@ namespace IRaCIS.Core.Domain.Share;
public static class StaticData
{
public static Dictionary<string, string> En_US_Dic = new Dictionary<string, string>();
public static Dictionary<string, string> Zh_CN_Dic = new Dictionary<string, string>();
public static Dictionary<string, string> Log_Locoalize_Dic = new Dictionary<string, string>();
public static Dictionary<string, string> Localizer_Dev_Dic = new Dictionary<string, string>();
#region 国际化
@ -28,48 +24,34 @@ public static class StaticData
public static readonly string en_US_bookMark = "en_us";
}
/// <summary>
/// 获取国际化
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string International(string key, params object?[] args)
{
try
{
return string.Format(GetLanguageDictionary(key), args);
}
catch (Exception)
{
#region 国际化废弃
return string.Empty;
}
}
//public static Dictionary<string, string> En_US_Dic = new Dictionary<string, string>();
//public static Dictionary<string, string> Zh_CN_Dic = new Dictionary<string, string>();
///// <summary>
///// 获取国际化
///// </summary>
///// <param name="data"></param>
///// <returns></returns>
//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];
// }
//}
public static string GetLanguageDictionary(string key)
{
try
{
var type = Thread.CurrentThread.CurrentUICulture.Name;
if (type == "zh-CN")
{
return Zh_CN_Dic[key];
#endregion
}
else
{
return En_US_Dic[key];
}
}
catch (Exception)
{
return string.Empty;
}
}
#endregion