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; using SqlSugar.IOC; using static System.Net.Mime.MediaTypeNames; 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 LiveUserInfo userInfo; readonly Stopwatch _stopwatch;//统计程序耗时 public HttpLogAttribute( LiveUserInfo userInfo) { _stopwatch = Stopwatch.StartNew(); this.userInfo = userInfo; } /// /// 执行接口前文件做缓存处理 /// /// /// public void ExecutingFileCached(ActionExecutingContext context) { //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 var endpoint = context.HttpContext.GetEndpoint(); // 直接返回原始结果,不封装 if (endpoint?.Metadata.GetMetadata() == null) return; if (context.HttpContext.Request.HasFormContentType && context.HttpContext.Request.Form.Files != null && context.HttpContext.Request.Form.Files.Count() > 0) { context.HttpContext.Items["FileCached"]= context.HttpContext.Request.Form.Files.Select(s => { var stream = new MemoryStream(); s.CopyTo(stream); stream.Position = 0; return (s, stream); }).ToArray(); } } /// /// 执行接口前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)) { //记录请求参数 if (context.Request.Body.CanSeek) { try { var fileArr = context.Items.ContainsKey("FileCached") ? context.Items["FileCached"] as (IFormFile file, MemoryStream stream)[] : null; if (context.Request.HasFormContentType && fileArr != null) { // 设置保存目录(例如:项目根目录下的Uploads文件夹) string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString()); // 创建目录(如果不存在) if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder); foreach (var fileInfo in fileArr) { // 生成安全文件名(防止路径遍历攻击) string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(fileInfo.file.FileName); // 保存文件 using var stream = new FileStream(Path.Combine(uploadsFolder, uniqueFileName), FileMode.Create, FileAccess.Write); fileInfo.stream.Position = 0; await fileInfo.stream.CopyToAsync(stream); fileInfo.stream.Dispose(); } request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}"; } else { context.Request.Body.Position = 0; using var sr = new System.IO.StreamReader(context.Request.Body); request = await sr.ReadToEndAsync(); } } catch (Exception ex) { request = "处理请求入参时发生了错误 \r\n" + ex.ToString() + "\r\n 原有请求数据 " + request; } } } //写入队列 await DbScoped.Sugar.Insertable(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 }).ExecuteCommandAsync(); } public override async void OnActionExecuting(ActionExecutingContext context) { Executing400(context); ExecutingFileCached(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 = code, 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; } } }