255 lines
9.7 KiB
C#
255 lines
9.7 KiB
C#
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;
|
||
|
||
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 LiveUserInfo userInfo;
|
||
readonly Stopwatch _stopwatch;//统计程序耗时
|
||
|
||
public HttpLogAttribute( LiveUserInfo userInfo)
|
||
{
|
||
_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 DbScoped.Sugar.Insertable<HttpLog>(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();
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// <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 = 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;
|
||
|
||
}
|
||
}
|
||
|
||
}
|