.net6 webapi 项目搭建(四、权限校验和异常日志的AOP实现)

484 阅读3分钟

一、认识 Filter 操作筛选器

微软官网教程

操作筛选器是一个属性,可应用于控制器操作(或整个控制器),该属性修改操作的执行方式; 可以在执行前后做点什么操作。

服务端缓存:

这里官网是这样描述的:

  • OutputCache – 此操作筛选器会缓存控制器操作的输出,以指定的时间量。

但是实际测试下来并没有用。

应该使用 ResponseCache

这里Duration 为缓存60秒 Snipaste_2022-08-09_17-44-34.png

两秒的是第一次请求; 2毫秒的是第二次请求;(两秒的是我设置了一个睡眠, 不是接口问题) Snipaste_2022-08-09_17-46-58.png

Snipaste_2022-08-09_17-50-00.png

缓存成功,可以用在一些固定的数据,比如地图资源和商品分类等。

筛选器类型

  • 授权筛选器 - 实现 IAuthorizationFilter 属性。
  • 操作筛选器 - 实现 IActionFilter 属性。
  • 结果筛选器 - 实现 IResultFilter 属性。
  • 异常筛选器 - 实现 IExceptionFilter 属性。

IActionFilter

Snipaste_2022-08-10_10-01-09.png

Log是自定的特性,继承自Attribute特性。

Action​Executing​Context 上下文

文档链接

IResultFilter

Snipaste_2022-08-10_10-57-16.png

Result​Executed​Context上下文

Snipaste_2022-08-10_10-54-21.png 文档链接

IAuthorizationFilter

Snipaste_2022-08-10_11-02-22.png

AuthorizationFilterContext授权筛选器上下文 Snipaste_2022-08-10_11-00-33.png 文档链接

IExceptionFilter

在引发异常时候触发 Snipaste_2022-08-10_11-13-17.png Snipaste_2022-08-10_11-14-31.png

ExceptionContext 上下文 Snipaste_2022-08-10_11-04-27.png 文档链接

二、Token权限校验

    /// <summary>
    /// 权限验证过滤器, 在控制器上添加Permission特性即可
    /// IAsyncActionFilter 异步方法校验
    /// </summary>
    public class PermissionAttribute : Attribute, IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            //获取请求头携带的token
            string token = context.HttpContext.Request.Headers["Authorization"];

            if(token != null)
            {
                //调用方法解析, 如果为null则解析失败不能通过
                TokenModelJwt model = JwtHelper.SerializeJwt(token);
                if(model != null)
                {
                    await next();
                }
                    
            }
            
            //设置自定义响应数据
            context.Result = new UnauthorizedObjectResult(HttpResult<object>.unauthorized("请先授权"));

        }
    }

Snipaste_2022-08-10_11-20-17.png

Snipaste_2022-08-10_11-19-34.png

Snipaste_2022-08-10_11-22-33.png

三、异常日志记录

serilog

Serilog 为文件、控制台和 其他地方提供诊断日志记录。它易于设置,具有干净的 API,并且可以在最近的 .NET 平台之间移植

使用

1.添加这两个Nuget包

Snipaste_2022-08-10_16-43-30.png

2.在Program.cs中配置

#region  初始化日志
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Error()
    .WriteTo.File(Path.Combine("Logs", @"Log.txt"), rollingInterval: RollingInterval.Day).CreateLogger();
#endregion

3.写入日志

Log.Error("测试日志写入");

Snipaste_2022-08-10_16-49-52.png

过滤器拦截异常写入日志

设置响应缓存

在program.cs中添加以下代码:

//使用响应缓存
app.UseResponseCaching();
app.Use((context, next) =>
{
    context.Request.EnableBuffering();
    return next();
});

如果不使用缓存的话, 在获取Request.body时Stream的长度为0;(就因为这个,浪费了我半天时间)

配置过滤器

新建一个LogAttribute特性过滤器, 并且继承Attribute和IExceptionFilter

public class LogAttribute : Attribute, IExceptionFilter
    {

        public async void OnException(ExceptionContext context)
        {
            StringBuilder buffer = new StringBuilder("\n");

            //1、记录报异常的ip
            IPAddress ip = context.HttpContext.Connection.RemoteIpAddress;
            //转为ipv4的公网地址
            if (ip.IsIPv4MappedToIPv6)
            {
                ip = ip.MapToIPv4();
            }
            buffer.Append($"公 网 IP:\t\t{ip}\n");

            //2、记录控制器名和接口名
            buffer.Append($"控制器名:\t\t{context.RouteData.Values["controller"].ToString()}\n");
            buffer.Append($"接 口 名:\t\t{context.RouteData.Values["action"].ToString()}\n");
           
            //3、获取请求的参数
            HttpRequest request = context.HttpContext.Request;
            buffer.Append($"请求方法:\t\t{request.Method}\n");
            buffer.Append($"请求路径:\t\t{request.Path}\n");
            buffer.Append($"网络协议:\t\t{request.Scheme}\n");
            string data = "";
            if (request.Method == "GET")
            {
                //get请求参数, url格式:?value=123&value2=asda 这种
                //可以做字符串的处理让他好看些, 反正我是看得懂
                data = context.HttpContext.Request.QueryString.Value;
            }
            else //放在body里的请求参数 json格式
            {
                //得到请求正文的数据流
                Stream stream = request.Body;
                //设置当前流的位置
                stream.Position = 0;
                using (StreamReader sr = new StreamReader(stream))
                {
                    data = await sr.ReadToEndAsync();
                };
                buffer.Append($"参数类型:\t\t{request.ContentType}\n");
            }
            if(data != "")
            {
                buffer.Append($"参    数:\t\t{data}\n");
            }

            //4、异常写入日志
            buffer.Append("异    常:\t\t");
            buffer.Append(context.Exception.ToString());
            buffer.Append("\n--------------------End--------------------\n\n"); //与下一条记录的拉开距离
            
            Log.Error(buffer.ToString()); 
        }

    }

在program.cs中配置,让他全局启用,不用再控制器上标记特性。

//设置日志筛选器的的全局过滤
builder.Services.AddMvc(option =>
{
    option.Filters.Add(typeof(LogAttribute));
});

来手动抛两个异常:

Snipaste_2022-08-12_09-06-50.png

写入的日志。