289 lines
11 KiB
C#
289 lines
11 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 SqlSugar.IOC;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using VideoAnalysisCore.Common.Dto;
|
||
using VideoAnalysisCore.Common;
|
||
using VideoAnalysisCore.Model;
|
||
|
||
namespace Learn.VideoAnalysis.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 Stopwatch _stopwatch;//统计程序耗时
|
||
|
||
public HttpLogAttribute()
|
||
{
|
||
_stopwatch = Stopwatch.StartNew();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行接口前文件做缓存处理
|
||
/// </summary>
|
||
/// <param name="context"></param>
|
||
/// <exception cref="CustomException"></exception>
|
||
public void ExecutingCached(ActionExecutingContext context)
|
||
{
|
||
//特殊处理:ResultIgnore,不进行返回结果包装,原样输出
|
||
var endpoint = context.HttpContext.GetEndpoint();
|
||
var request = context.HttpContext.Request;
|
||
// 1. 只有非 GET 请求且不是文件上传时才读取 Body
|
||
if (!request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase)
|
||
&& !request.HasFormContentType)
|
||
{
|
||
// 确保从头开始读
|
||
request.Body.Position = 0;
|
||
// 使用 leaveOpen: true 防止 StreamReader 关闭底层的 Request.Body
|
||
using (var reader = new System.IO.StreamReader(request.Body, System.Text.Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true))
|
||
{
|
||
var body = reader.ReadToEnd();
|
||
context.HttpContext.Items["RequestBodyRaw"] = body;
|
||
request.Body.Position = 0;
|
||
}
|
||
}
|
||
// 直接返回原始结果,不封装
|
||
if (endpoint?.Metadata.GetMetadata<HttpLogEnable>() == 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();
|
||
}
|
||
|
||
|
||
|
||
}
|
||
/// <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)
|
||
{
|
||
string request = null;
|
||
var logId = Yitter.IdGenerator.YitIdHelper.NextId();
|
||
if (!context.Request.Method.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
|
||
{
|
||
var fileArr = context.Items.ContainsKey("FileCached") ? context.Items["FileCached"] as (IFormFile file, MemoryStream stream)[] : null;
|
||
if (context.Request.HasFormContentType && fileArr != null)
|
||
{
|
||
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}";
|
||
}
|
||
|
||
}
|
||
if (context.Items.ContainsKey("RequestBodyRaw"))
|
||
{
|
||
request = context.Items["RequestBodyRaw"] as string;
|
||
}
|
||
//写入队列
|
||
await DbScoped.Sugar.CopyNew()
|
||
.Insertable<HttpLog>(new HttpLog
|
||
{
|
||
Id = logId,
|
||
Url = context.Request.Path + context.Request.QueryString,
|
||
Method = context.Request.Method,
|
||
Request = request,
|
||
IP = GetClientIp(context),
|
||
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 = 0,
|
||
TotalMilliseconds = (double)_stopwatch.Elapsed.TotalMilliseconds
|
||
}).ExecuteCommandAsync();
|
||
}
|
||
|
||
private string GetClientIp(HttpContext context)
|
||
{
|
||
var headers = context.Request.Headers;
|
||
var ip = headers["X-Forwarded-For"].FirstOrDefault();
|
||
if (!string.IsNullOrWhiteSpace(ip))
|
||
{
|
||
ip = ip.Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim();
|
||
}
|
||
if (string.IsNullOrWhiteSpace(ip))
|
||
{
|
||
ip = headers["X-Real-IP"].FirstOrDefault();
|
||
}
|
||
if (string.IsNullOrWhiteSpace(ip) && context.Connection.RemoteIpAddress != null)
|
||
{
|
||
ip = context.Connection.RemoteIpAddress.ToString();
|
||
}
|
||
return ip ?? string.Empty;
|
||
}
|
||
|
||
public override async void OnActionExecuting(ActionExecutingContext context)
|
||
{
|
||
|
||
Executing400(context);
|
||
ExecutingCached(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)
|
||
{
|
||
Console.WriteLine($"{DateTime.Now}=>接口规范出现异常");
|
||
Console.WriteLine(ex.Message);
|
||
Console.WriteLine(ex.StackTrace);
|
||
}
|
||
|
||
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;
|
||
|
||
}
|
||
}
|
||
|
||
}
|