using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using Microsoft.AspNetCore.Http; using SqlSugar; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Diagnostics; using System.Text.Json; using System.Collections.Generic; using System.Data; using Microsoft.Extensions.Hosting; using System.IO; using Microsoft.AspNetCore.Hosting; using Learn.Archives.Core.Common; using Learn.Archives.Core.Model.Dto; using Learn.Archives.Core.Model; namespace Learn.Archives.API.Expand { /// /// 使用该属性,接口对结果原样输出,不做包装 /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] public class ResultIgnore : Attribute { } /// /// http接口日志启用 /// public class HttpLogEnable : Attribute { } /// /// Http请求过滤器 /// public class HttpLogAttribute : ActionFilterAttribute, IAsyncExceptionFilter { readonly Repository logService; readonly LiveUserInfo userInfo; readonly Stopwatch _stopwatch;//统计程序耗时 public HttpLogAttribute(Repository logService, LiveUserInfo userInfo) { this.logService = logService; _stopwatch = Stopwatch.StartNew(); this.userInfo = userInfo; } /// /// 执行接口前400 处理 /// /// /// public void Executing400(ActionExecutingContext context) { if (!context.ModelState.IsValid) { var errMsg = string.Join(',', context.ModelState.Values.SelectMany(s => s.Errors.Select(e => e.ErrorMessage))); Oh.ModelError(errMsg); } } private bool HasAttribute(ActionExecutedContext context) where T : Attribute { if (context.ActionDescriptor is ControllerActionDescriptor descriptor) { // 检查方法上是否有 SkipApiResultAttribute if (descriptor.MethodInfo.GetCustomAttributes(typeof(T), false).Any()) return true; // 检查控制器上是否有 SkipApiResultAttribute if (descriptor.ControllerTypeInfo.GetCustomAttributes(typeof(T), false).Any()) return true; } return false; } /// /// 接口结果格式化 /// /// public BaseReturn? ApiResultFormatting(ActionExecutedContext context) { //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 if (HasAttribute(context)) { base.OnActionExecuted(context); return null; } // 返回结果为JsonResult的请求进行Result包装 if (context.Exception != null) throw context.Exception; if (context.Result != null) { object? resData = null; if (context.Result is ObjectResult objectResult) resData = objectResult.Value; else if (context.Result is ContentResult contentRes) resData = contentRes.Content; else if (context.Result is JsonResult resJ) resData = resJ.Value; else if (context.Result is FileResult) return null; var code = (context?.Result as IStatusCodeActionResult)?.StatusCode ?? 200; var res = new BaseReturn() { Code = code, Data = resData, Message = "SUCCESS" }; context.Result = new JsonResult(res); return res; } return null; } /// /// 添加http日志信息 /// /// /// /// /// public async Task AddHttpLogAsync(HttpContext context, BaseReturn? result = null, Exception? e = null) { //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 var endpoint = context.GetEndpoint(); // 直接返回原始结果,不封装 if (endpoint?.Metadata.GetMetadata() == null) return; string request = null; var logId = Yitter.IdGenerator.YitIdHelper.NextId(); if (!context.Request.Method .Equals("GET", StringComparison.InvariantCultureIgnoreCase)) { context.Request.EnableBuffering(); //记录请求参数 if (context.Request.Body.CanSeek) { try { if (context.Request.HasFormContentType && context.Request?.Form?.Files?.Count() > 0) { // 设置保存目录(例如:项目根目录下的Uploads文件夹) string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString()); // 创建目录(如果不存在) if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder); foreach (var file in context.Request.Form.Files) { // 生成安全文件名(防止路径遍历攻击) string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(file.FileName); string filePath = Path.Combine(uploadsFolder, uniqueFileName); // 保存文件 using var stream = new FileStream(filePath, FileMode.Create); await file.CopyToAsync(stream); } request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}"; } else { context.Request.Body.Position = 0; using var sr = new StreamReader(context.Request.Body); request = await sr.ReadToEndAsync(); } } catch (Exception ex) { request = "处理请求日志时发生了错误 \r\n" + ex.ToString(); } } } //写入队列 await logService.InsertAsync(new HttpLog { Id = logId, Url = context.Request.Path + context.Request.QueryString, Method = context.Request.Method, Request = request, IP = context.Connection?.RemoteIpAddress?.ToString(), ResponseCode = result?.Code ?? -1, Response = result != null ? JsonSerializer.Serialize(result) : null, Authorization = context.Request.Headers.ContainsKey("Authorization") ? context.Request.Headers["Authorization"].ToString() : string.Empty, Exception = e?.ToString(), ExceptionMessage = e?.Message, AdminId = userInfo.Id , TotalMilliseconds = (double)_stopwatch.Elapsed.TotalMilliseconds }); } /// /// 在Controller的Action执行前执行 /// /// public override void OnActionExecuting(ActionExecutingContext context) { Executing400(context); base.OnActionExecuting(context); } /// /// 在Controller的Action执行后执行 /// /// public override async void OnActionExecuted(ActionExecutedContext context) { try { BaseReturn? res = ApiResultFormatting(context); await AddHttpLogAsync(context.HttpContext, res); } catch (Exception ex) { } //添加http请求日志 base.OnActionExecuted(context); } /// /// 执行错误时 /// /// /// public async Task OnExceptionAsync(ExceptionContext context) { var code = -1; var msg = context.Exception.Message; if (context.Exception is OhException exception) code = exception.Code; var result = new BaseReturn() { Code = -1, Message = context.Exception.Message }; context.Result = new JsonResult(result); await AddHttpLogAsync(context.HttpContext, null, context.Exception); if (code == 401 || code == 403) context.HttpContext.Response.StatusCode = code; context.ExceptionHandled = true; } } }