From 37e8545eba115a333dd4b49b4f955a2d035f449a Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 6 Feb 2026 21:12:52 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E9=80=9A=E7=9F=A5=E6=8E=A7=E5=88=B6=E5=BC=80?= =?UTF-8?q?=E5=85=B3=EF=BC=8C=E5=90=8C=E6=97=B6=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=88=86=E5=88=AB=E9=80=9A=E7=9F=A5=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E7=9A=84=E4=BA=BA=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/appsettings.json | 4 +- .../LegacyController/ModelActionFilter .cs | 51 +++++++++- .../ProjectExceptionFilter.cs | 30 ++++-- .../Helper/OtherTool/WeComNotifier.cs | 95 +++++++++++-------- IRaCIS.Core.Application/TestService.cs | 2 +- 5 files changed, 128 insertions(+), 54 deletions(-) diff --git a/IRaCIS.Core.API/appsettings.json b/IRaCIS.Core.API/appsettings.json index 46d4d60fe..a39b1b988 100644 --- a/IRaCIS.Core.API/appsettings.json +++ b/IRaCIS.Core.API/appsettings.json @@ -44,8 +44,10 @@ ] }, "WeComNoticeConfig": { + "IsOpenWeComNotice": true, "WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322 - "NoticeUserList": [ "ZhouHang" ] + "APINoticeUserList": [ "ZhouHang" ], + "VueNoticeUserList": [ "wangxiaoshuang" ] }, "IRaCISImageStore": { "SwitchingMode": "RemainingDiskCapacity", diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index ee1d392cf..65552ebcf 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -1,18 +1,22 @@ -using Microsoft.AspNetCore.Mvc; +using IRaCIS.Core.Application.Helper.OtherTool; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Localization; using Newtonsoft.Json; +using System; using System.Reflection; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace IRaCIS.Core.Application.Filter; -public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter +public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : ActionFilterAttribute, IActionFilter { @@ -27,6 +31,49 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib .Select(e => e.ErrorMessage) .ToArray(); + var request = context.HttpContext.Request; + + try + { + bool isOpenWeComNotice = _config.GetValue("WeComNoticeConfig:IsOpenWeComNotice"); + + if (isOpenWeComNotice) + { + string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty; + + var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]); + var baseUrl = uri.GetLeftPart(UriPartial.Authority); + + var userList = _config.GetSection("WeComNoticeConfig:VueNoticeUserList").Get(); + + var requestBody = context.HttpContext.Items["RequestBody"] as string; + + + // 🔔 异步告警(不要阻塞请求) + _ = WeComNotifier.SendAlertAsync( + webhook: webhook, + alert: new WeComAlert + { + Env = baseUrl, + UserName = _userInfo.UserName.IsNotNullOrEmpty()? $"{_userInfo.UserName}({_userInfo.UserTypeShortName})": "匿名", + Api = $"{request.Method} {request.Path}", + Message = $"前端传递参数,后端模型验证失败\n 具体信息:{JsonConvert.SerializeObject(validationErrors)}", + JsonData = requestBody, + AtUsers = userList ?? [] + } + ); + + } + + + } + catch (Exception ex) + { + Log.Logger.Error($"模型验证过滤器里发送企业微信出现错误:{ex.Message}"); + } + + + //---提供给接口的参数无效。 context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject(validationErrors))); } diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs index 3231e9059..eeb70dcc9 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs @@ -53,27 +53,41 @@ public class ProjectExceptionFilter(ILogger _logger, ISt try { - string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty; - var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]); - var baseUrl = uri.GetLeftPart(UriPartial.Authority); + bool isOpenWeComNotice = _config.GetValue("WeComNoticeConfig:IsOpenWeComNotice"); - var userList = _config.GetSection("WeComNoticeConfig:NoticeUserList").Get(); + if (isOpenWeComNotice) + { + string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty; + var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]); + var baseUrl = uri.GetLeftPart(UriPartial.Authority); + var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get(); - var requestBody = context.HttpContext.Items["RequestBody"] as string; + var requestBody = context.HttpContext.Items["RequestBody"] as string; + var alert = new WeComAlert + { + Env = baseUrl, + UserName = $"{_userInfo.UserName}({_userInfo.UserTypeShortName})", + Api = context.HttpContext.Request.Path, + Message = exception.Message, + JsonData = requestBody, + Stack = exception.StackTrace, + AtUsers = userList ?? [] + }; + _ = WeComNotifier.SendAlertAsync(webhook, alert); + } - - WeComNotifier.SendErrorAsync(webhook, baseUrl, $"{_userInfo.UserName}({_userInfo.UserTypeShortName})", context.HttpContext.Request.Path, requestBody, exception, userList).GetAwaiter().GetResult(); + } catch (Exception ex) { _logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}"); } - + } context.ExceptionHandled = true;//标记当前异常已经被处理过了 diff --git a/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs b/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs index 641b117b1..db100aa68 100644 --- a/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs +++ b/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs @@ -12,76 +12,87 @@ using IdentityModel; namespace IRaCIS.Core.Application.Helper.OtherTool; +public class WeComAlert +{ + public string Env { get; set; } = ""; + public string UserName { get; set; } = ""; + public string Api { get; set; } = ""; + public string Message { get; set; } = ""; + public string? JsonData { get; set; } + public string? Stack { get; set; } + public bool HasStack => !string.IsNullOrWhiteSpace(Stack); + public string[] AtUsers { get; set; } = []; +} + + public static class WeComNotifier { - - public static async Task SendErrorAsync(string webhook, string env, string userName,string api,string json, Exception ex, params string[] atUsers) + + public static async Task SendAlertAsync(string webhook, WeComAlert alert) { try { var client = new RestClient(); var request = new RestRequest(webhook, Method.Post); - string hostName = Dns.GetHostName(); - string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - var stack = ex.StackTrace ?? "无堆栈信息"; - stack = stack.Replace("\n", "\n> "); // 每行变成引用,防止markdown断裂 - if (stack.Length > 1200) - stack = stack.Substring(0, 1200) + "...(已截断)"; + // 处理 @ + var atText = alert.AtUsers != null && alert.AtUsers.Any() + ? string.Join("", alert.AtUsers.Select(u => $" <@{u}>")) + : ""; - string atText = ""; - if (atUsers != null && atUsers.Any()) + // 处理堆栈 + var stack = alert.Stack; + if (!string.IsNullOrWhiteSpace(stack)) { - foreach (var u in atUsers) - { - atText += $" <@{u}>"; // 注意空格分隔 - } + stack = stack.Replace("\n", "\n> "); + if (stack.Length > 1200) + stack = stack[..1200] + "...(已截断)"; } + var markdown = $@"## 🚨 系统告警 +> {atText} +> **部署环境:** [{alert.Env}]({alert.Env}) +> **发生时间:** {time} +> **操作人:** {alert.UserName} +> **接口地址:** {alert.Api} +### ❗ 告警信息 +{alert.Message}"; - //> **运行环境:** < font color = ""comment"" >{ EnvironmentName} danger danger warning - var markdown = $@"## 🚨 系统异常告警 - > {atText} - > **部署环境:** [{env}]({env}) - > **服务器:** {hostName} - > **发生时间:** {time} - > **操作人:** {userName} - > **接口地址:** {api} - ### ❗ 异常信息 - {ex.Message} - >**📦 请求数据(JSON 格式):** + if (!string.IsNullOrWhiteSpace(alert.JsonData)) + { + markdown += $@" +> **📦 请求数据(JSON 格式):** ```json -{json} -``` - ### 堆栈信息(部分) - > {stack} - "; +{alert.JsonData} +```"; + } + + if (!string.IsNullOrWhiteSpace(stack)) + { + markdown += $@" +### 堆栈信息(部分) +{stack}"; + } var payload = new { msgtype = "markdown", - markdown = new - { - content = markdown - } + markdown = new { content = markdown } }; request.AddHeader("Content-Type", "application/json"); request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json); - var response = await client.ExecuteAsync(request); - - if (!response.IsSuccessful) - { - Log.Logger.Error($"企业微信通知失败: {response.StatusCode} {response.ErrorMessage}"); - } + await client.ExecuteAsync(request); } - catch (Exception notifyEx) + catch (Exception ex) { - Log.Logger.Error("发送企业微信告警异常: " + notifyEx.Message); + Log.Logger.Error("企业微信告警发送失败: " + ex.Message); } } + } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 591a1ec2a..a4ce65daf 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -116,7 +116,7 @@ namespace IRaCIS.Core.Application.Service var webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=cdd97aab-d256-4f07-9145-a0a2b1555322"; //await WeComNotifier.SendErrorAsync(webhook, "http://irc.test.extimaging.com/login", new Exception("测试异常"), new[] { "ZhouHang" }); - throw new Exception("手动测试异常抛出"); + //throw new Exception("手动测试异常抛出"); return ResponseOutput.Ok(modelVerify); } From 746a8de799beb1801b807fd0dab3157c1696a61c Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Mon, 9 Feb 2026 17:12:12 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Reading/Dto/ReadingQuestionViewModel.cs | 2 ++ IRaCIS.Core.Application/Service/Reading/_MapConfig.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/IRaCIS.Core.Application/Service/Reading/Dto/ReadingQuestionViewModel.cs b/IRaCIS.Core.Application/Service/Reading/Dto/ReadingQuestionViewModel.cs index 98e91daec..980465de0 100644 --- a/IRaCIS.Core.Application/Service/Reading/Dto/ReadingQuestionViewModel.cs +++ b/IRaCIS.Core.Application/Service/Reading/Dto/ReadingQuestionViewModel.cs @@ -985,6 +985,8 @@ namespace IRaCIS.Core.Application.Service.Reading.Dto public string QuestionGroupName { get; set; } + public string QuestionGroupEnName { get; set; } + /// /// 影像工具 /// diff --git a/IRaCIS.Core.Application/Service/Reading/_MapConfig.cs b/IRaCIS.Core.Application/Service/Reading/_MapConfig.cs index ade184f49..96949ccdd 100644 --- a/IRaCIS.Core.Application/Service/Reading/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/Reading/_MapConfig.cs @@ -375,6 +375,7 @@ namespace IRaCIS.Core.Application.Service .ForMember(dest => dest.CreateUserRole, opt => opt.Ignore()); CreateMap() .ForMember(d => d.QuestionGroupName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupName : s.GroupInfo.GroupName)) + .ForMember(d => d.QuestionGroupEnName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupEnName : s.GroupInfo.GroupEnName)) .ForMember(d => d.GroupName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupName : s.GroupInfo.GroupName)) .ForMember(d => d.GroupEnName, u => u.MapFrom(s => s.GroupInfo == null ? s.GroupEnName : s.GroupInfo.GroupEnName)) .ForMember(d => d.ParentQuestionName, u => u.MapFrom(s => s.ParentReadingQuestionTrial == null ? string.Empty : s.ParentReadingQuestionTrial.QuestionName)) From 72ff2bbf151835e686514803fcd02ff6a9f9423f Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Mon, 9 Feb 2026 20:32:19 -0500 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/SiteSurvey/TrialSiteSurveyService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs index 9448314a2..7a785d7be 100644 --- a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs +++ b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs @@ -254,16 +254,19 @@ namespace IRaCIS.Core.Application.Contracts { isVirtual = await _trialRepository.Where(x => x.Id == inDto.TrialId).Select(x => x.TrialType != TrialType.OfficialTrial).FirstNotNullAsync(); } + Doctor doctor = new Doctor() { EMail = inDto.EmailOrPhone, IsVirtual = isVirtual, - AcceptingNewTrial = false, + AcceptingNewTrial = inDto.TrialId == null ? false : true, ActivelyReading = false, - CooperateStatus = ContractorStatusEnum.Noncooperation, + ResumeStatus = ResumeStatusEnum.Pass, + CooperateStatus = inDto.TrialId == null ? ContractorStatusEnum.Noncooperation : ContractorStatusEnum.Cooperation, ReviewStatus = ReviewerInformationConfirmStatus.ConfirmRefuse }; + doctor.Code = await _doctorRepository.Select(t => t.Code).DefaultIfEmpty().MaxAsync() + 1; doctor.ReviewerCode = AppSettings.GetCodeStr(doctor.Code, nameof(Doctor)); var info = await _doctorRepository.AddAsync(doctor, true); From 1f7b96d9f0f5b90ebabd1d6767fe0f09e59ed424 Mon Sep 17 00:00:00 2001 From: he <109787524@qq.com> Date: Mon, 9 Feb 2026 20:36:30 -0500 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/SiteSurvey/TrialSiteSurveyService.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs index 7a785d7be..097891d95 100644 --- a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs +++ b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs @@ -154,7 +154,7 @@ namespace IRaCIS.Core.Application.Contracts { isVirtual = await _trialRepository.Where(x => x.Id == inDto.TrialId).Select(x => x.TrialType != TrialType.OfficialTrial).FirstNotNullAsync(); - await _doctorRepository.UpdatePartialFromQueryAsync(x => x.Id == dockerInfo.Id, y => new Doctor() + await _doctorRepository.BatchUpdateNoTrackingAsync(x => x.Id == dockerInfo.Id, y => new Doctor() { IsVirtual = isVirtual, AcceptingNewTrial = inDto.TrialId == null ? false : true, @@ -245,6 +245,17 @@ namespace IRaCIS.Core.Application.Contracts { result.DoctorId = dockerInfo.Id; result.ReviewStatus = dockerInfo.ReviewStatus; + + if(inDto.TrialId != null) + { + var isVirtual = await _trialRepository.Where(x => x.Id == inDto.TrialId).Select(x => x.TrialType != TrialType.OfficialTrial).FirstNotNullAsync(); + await _doctorRepository.BatchUpdateNoTrackingAsync(x => x.Id == dockerInfo.Id, y => new Doctor() + { + IsVirtual = isVirtual, + AcceptingNewTrial = inDto.TrialId == null ? false : true, + CooperateStatus = inDto.TrialId == null ? ContractorStatusEnum.Noncooperation : ContractorStatusEnum.Cooperation, + }); + } } else {