diff --git a/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs b/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs index ee61eabbc..c1686fd55 100644 --- a/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs +++ b/IRaCIS.Core.API/_PipelineExtensions/Serilog/RequestResponseLoggingMiddleware.cs @@ -39,6 +39,8 @@ namespace IRaCIS.Core.API._PipelineExtensions.Serilog var requestBodyPayload = await ReadRequestBody(context.Request); + context.Items["RequestBody"] = requestBodyPayload; + using (LogContext.PushProperty("RequestBody", requestBodyPayload)) { //await _next.Invoke() diff --git a/IRaCIS.Core.API/appsettings.json b/IRaCIS.Core.API/appsettings.json index 5376f4890..a39b1b988 100644 --- a/IRaCIS.Core.API/appsettings.json +++ b/IRaCIS.Core.API/appsettings.json @@ -3,7 +3,7 @@ "SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021", "Issuer": "Extimaging", "Audience": "EICS", - "TokenExpireMinute": "10080"//7天 + "TokenExpireMinute": "10080" //7天 }, "IpRateLimiting": { "EnableEndpointRateLimiting": true, @@ -43,6 +43,12 @@ } ] }, + "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 + "APINoticeUserList": [ "ZhouHang" ], + "VueNoticeUserList": [ "wangxiaoshuang" ] + }, "IRaCISImageStore": { "SwitchingMode": "RemainingDiskCapacity", "SwitchingRatio": 80, 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 930c3d9cb..eeb70dcc9 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ProjectExceptionFilter.cs @@ -1,13 +1,16 @@ -using IRaCIS.Core.Domain.Share; +using IRaCIS.Core.Application.Helper.OtherTool; +using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infrastructure; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; namespace IRaCIS.Core.Application.Filter; -public class ProjectExceptionFilter(ILogger _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter +public class ProjectExceptionFilter(ILogger _logger, IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) @@ -48,6 +51,43 @@ public class ProjectExceptionFilter(ILogger _logger, ISt context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message) : (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException)); + 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:APINoticeUserList").Get(); + + 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); + } + + + } + catch (Exception ex) + { + + _logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}"); + } + } context.ExceptionHandled = true;//标记当前异常已经被处理过了 @@ -58,6 +98,7 @@ public class ProjectExceptionFilter(ILogger _logger, ISt _logger.LogError(errorInfo); + //_logger.LogError(exception, exception.Message); } else diff --git a/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs b/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs new file mode 100644 index 000000000..db100aa68 --- /dev/null +++ b/IRaCIS.Core.Application/Helper/OtherTool/WeComNotifier.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using RestSharp; +using System.Net; +using System.Threading.Tasks; +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 SendAlertAsync(string webhook, WeComAlert alert) + { + try + { + var client = new RestClient(); + var request = new RestRequest(webhook, Method.Post); + + var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + + // 处理 @ + var atText = alert.AtUsers != null && alert.AtUsers.Any() + ? string.Join("", alert.AtUsers.Select(u => $" <@{u}>")) + : ""; + + // 处理堆栈 + var stack = alert.Stack; + if (!string.IsNullOrWhiteSpace(stack)) + { + 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}"; + + if (!string.IsNullOrWhiteSpace(alert.JsonData)) + { + markdown += $@" +> **📦 请求数据(JSON 格式):** +```json +{alert.JsonData} +```"; + } + + if (!string.IsNullOrWhiteSpace(stack)) + { + markdown += $@" +### 堆栈信息(部分) +{stack}"; + } + + var payload = new + { + msgtype = "markdown", + markdown = new { content = markdown } + }; + + request.AddHeader("Content-Type", "application/json"); + request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json); + + await client.ExecuteAsync(request); + } + catch (Exception ex) + { + Log.Logger.Error("企业微信告警发送失败: " + ex.Message); + } + } + +} + diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 5d65f40dc..a4ce65daf 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -6,6 +6,7 @@ using IRaCIS.Application.Contracts; using IRaCIS.Core.Application.BusinessFilter; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Helper; +using IRaCIS.Core.Application.Helper.OtherTool; using IRaCIS.Core.Application.Service.BusinessFilter; using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Domain; @@ -109,10 +110,13 @@ namespace IRaCIS.Core.Application.Service //创建一个模型验证的方法 [AllowAnonymous] - [HttpPost] + [HttpPost("{email}")] public async Task PostModelVerify(ModelVerifyCommand modelVerify) { + 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("手动测试异常抛出"); return ResponseOutput.Ok(modelVerify); } diff --git a/irc_api.drone.yml b/irc_api.drone.yml index 785ee716c..f06092d78 100644 --- a/irc_api.drone.yml +++ b/irc_api.drone.yml @@ -43,10 +43,16 @@ server: from_secret: test_ssh_pwd steps: -- name: publish-test-irc - commands: - - bash /opt/1panel/xc-deploy-new/Test_IRC_Swarm/devops-publish/test-branch-publish.sh - + - name: publish-test-irc + commands: + - bash /opt/1panel/xc-deploy-new/Test_IRC_Swarm/devops-publish/test-branch-publish.sh + - name: notify-wecom + commands: + - bash /opt/1panel/xc-deploy-new/devops-center/drone-notify-wecom.sh "$DRONE_BUILD_STATUS" "$DRONE_REPO_NAME" "$DRONE_BRANCH" "$DRONE_BUILD_NUMBER" "4355b98e-1e72-4678-8dfb-2fc6ad0bf449" "$DRONE_COMMIT_MESSAGE" "$DRONE_COMMIT_AUTHOR" "Test_IRC_API Test_IRC_SCP_API" "irc.test.extimaging.com" + when: + status: + - success + - failure trigger: branch: - Test_IRC_Net8 @@ -81,32 +87,7 @@ trigger: branch: - Test_Study_Net8 ---- -kind: pipeline -type: ssh -name: ssh-linux-test-hir -platform: - os: Linux - arch: 386 - -clone: - disable: true #禁用默认克隆 - -server: - host: 106.14.89.110 - user: root - password: - from_secret: test_ssh_pwd - -steps: -- name: publish-test-hir - commands: - - bash /opt/1panel/xc-deploy-new/Test_HIR/devops-publish/test-branch-publish.sh - -trigger: - branch: - - Test_HIR