Learn.Archives/Learn.Archives.API/Expand/HttpFilter.cs

256 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 使用该属性,接口对结果原样输出,不做包装
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public class ResultIgnore : Attribute { }
/// <summary>
/// http接口日志启用
/// </summary>
public class HttpLogEnable : Attribute { }
/// <summary>
/// Http请求过滤器
/// </summary>
public class HttpLogAttribute : ActionFilterAttribute, IAsyncExceptionFilter
{
readonly Repository<HttpLog> logService;
readonly LiveUserInfo userInfo;
readonly Stopwatch _stopwatch;//统计程序耗时
public HttpLogAttribute(Repository<HttpLog> logService, LiveUserInfo userInfo)
{
this.logService = logService;
_stopwatch = Stopwatch.StartNew();
this.userInfo = userInfo;
}
/// <summary>
/// 执行接口前400 处理
/// </summary>
/// <param name="context"></param>
/// <exception cref="CustomException"></exception>
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<T>(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;
}
/// <summary>
/// 接口结果格式化
/// </summary>
/// <param name="context"></param>
public BaseReturn<object>? ApiResultFormatting(ActionExecutedContext context)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
if (HasAttribute<ResultIgnore>(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<object>()
{
Code = code,
Data = resData,
Message = "SUCCESS"
};
context.Result = new JsonResult(res);
return res;
}
return null;
}
/// <summary>
/// 添加http日志信息
/// </summary>
/// <param name="context"></param>
/// <param name="result"></param>
/// <param name="e"></param>
/// <returns></returns>
public async Task AddHttpLogAsync(HttpContext context, BaseReturn<object>? result = null, Exception? e = null)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
var endpoint = context.GetEndpoint();
// 直接返回原始结果,不封装
if (endpoint?.Metadata.GetMetadata<HttpLogEnable>() == 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
});
}
/// <summary>
/// 在Controller的Action执行前执行
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
Executing400(context);
base.OnActionExecuting(context);
}
/// <summary>
/// 在Controller的Action执行后执行
/// </summary>
/// <param name="context"></param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
try
{
BaseReturn<object>? res = ApiResultFormatting(context);
await AddHttpLogAsync(context.HttpContext, res);
}
catch (Exception ex)
{
}
//添加http请求日志
base.OnActionExecuted(context);
}
/// <summary>
/// 执行错误时
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
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;
}
}
}