zoukankan      html  css  js  c++  java
  • 第三节:必备中间件集成2(缓存、认证授权、自定义黑名单、日志等)

    一. 缓存

     参考文章:

     (1). Asp.Net Core内存缓存:https://www.cnblogs.com/yaopengfei/p/11043337.html

     (2). Asp.Net Core分布式缓存(SQLServer和Redis):https://www.cnblogs.com/yaopengfei/p/11121984.html

     (3). Redis系列:https://www.cnblogs.com/yaopengfei/p/13870561.html

     (4).CSRedisCore的用法:https://www.cnblogs.com/yaopengfei/p/14211883.html

    1. 说明

     Asp.Net Core默认的缓存Api相对单调,我们通常直接调用Redis进行处理,这里我们在YpfCore.Utils统一封装,封装了CSRedisCore和StackExchange.Redis两个程序集来调用Redis,推荐使用CSRedisCore程序集,基于该程序集这里既初始化Core Mvc框架的缓存,也实例化了CSRedisCore的全局调用对象。

     需要安装的程序集有:【CSRedisCore】【Caching.CSRedis】和【StackExchange.Redis】

    策略类CacheStrategyExtensions代码

        /// <summary>
        /// 缓存策略扩展
        /// </summary>
        public static class CacheStrategyExtensions
        {
            /// <summary>
            /// 添加缓存类型
            /// (最后无论哪种模式,都把AddMemoryCache注入,方便单独使用IMemoryCache)(视情况而定)
            /// </summary>
            /// <param name="services"></param>
            /// <param name="CacheType">有4种取值 (Redis:代表基于CSRedisCore使用redis缓存, 并实例化redis相关对象. Memory:代表使用内存缓存; 
            /// StackRedis: 代表基于StackExchange.Redis初始化; "null":表示什也不注入)</param>
            /// <returns></returns>
            public static IServiceCollection AddCacheStrategy(this IServiceCollection services, string CacheType)
            {
                switch (CacheType)
                {
                    case "Memory": services.AddDistributedMemoryCache(); break;
                    case "Redis":
                        {
                            //基于CSRedisCore初始化
                            //初始化redis的两种使用方式
                            var csredis = new CSRedisClient(ConfigHelp.GetString("RedisStr"));
                            services.AddSingleton(csredis);
                            RedisHelper.Initialization(csredis);
    
                            //初始化缓存基于redis
                            services.AddSingleton<IDistributedCache>(new CSRedisCache(csredis));
                        }; break;
                    case "StackRedis":
                        {
                            //基于StackExchange.Redis初始化(该程序集这里不初始化缓存)
                            var connectionString = ConfigHelp.GetString("RedisStr");
                            //int defaultDB = Convert.ToInt32(ConfigHelp.GetString("RedisStr:defaultDB"));
                            services.AddSingleton(new SERedisHelp(connectionString));
                        }; break;
                    case "null":
                        {
                            //什么也不注入
                        }; break;
                    default: throw new Exception("缓存类型无效");
                }
                //最后都把AddMemoryCache注入,方便单独使用IMemoryCache进行内存缓存(视情况而定)
                //services.AddMemoryCache();
    
                return services;
            }
        }

    StackExchange.Redis相关代码封装

        /// <summary>
        /// redis链接帮助类 
        /// 基于程序集:StackExchange.Redis
        /// </summary>
        public class SERedisHelp
        {
    
            private string _connectionString; //连接字符串
            private int _defaultDB; //默认数据库
            private readonly ConnectionMultiplexer connectionMultiplexer;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="connectionString"></param>
            /// <param name="defaultDB">默认使用Redis的0库</param>
            public SERedisHelp(string connectionString, int defaultDB = 0)
            {
                _connectionString = connectionString;
                _defaultDB = defaultDB;
                connectionMultiplexer = ConnectionMultiplexer.Connect(_connectionString);
            }
    
            /// <summary>
            /// 获取数据库
            /// </summary>
            /// <returns></returns>
            public IDatabase GetDatabase()
            {
                return connectionMultiplexer.GetDatabase(_defaultDB);
            }
        }
    View Code

    ConfigureService中注入

    //添加redis实例化配置和缓存策略
    services.AddCacheStrategy(_Configuration["CacheType"]);

    配置文件

      //缓存类型
      //有4种取值 (Redis:代表基于CSRedisCore使用redis缓存, 并实例化redis相关对象. Memory:代表使用内存缓存; StackRedis: 代表基于StackExchange.Redis初始化; "null":表示什也不注入)
      "CacheType": "null",
      "RedisStr": "xxx.45.xxx.249:6379,password=123456,defaultDatabase=0"

    2. 测试

     将配置文件中的CacheType类型改为“Redis”,然后在控制器中注入IDistributedCache调用缓存Api  或  直接使用RedisHelper类操控Redis各种数据结构即可。

    代码分享:

                {
                    //1.缓存的用法(redis or 内存主要看配置)
                    _Cache.SetString("name1", "ypf1");
                    var data1 = _Cache.GetString("name1");
    
                    //2. redis的操控
                    RedisHelper.HSet("myhash", "name2", "ypf2");
                    var data2 = RedisHelper.HGet("myhash", "name2");
                }

    二. 认证授权

    参考文章:

     (1). 关于jwt的认证授权:https://www.cnblogs.com/yaopengfei/p/12162507.html

     (2). 关于grpc的认证授权:https://www.cnblogs.com/yaopengfei/p/13403001.html

     (3). 补充集中其它校验方式:https://www.cnblogs.com/yaopengfei/p/10468728.html

     (4). 基于IDS4相关:https://www.cnblogs.com/yaopengfei/p/12885217.html

    1.说明

     目前该系统主要做两层校验,是否登录(前后端不分离的时候使用,借助Session),是否合法(jwt校验,可以用于前后端分离或者不分离)。

    (1). 是否登录校验思路

     A. 登录成功后,将部分用户信息存入Session。

     B. 编写SkipLogin特性用于标记方法跳过登录校验。

     C. 编写过滤器,判断Session中是否有值,从而决定继续 or 驳回。(区分是否是Ajax请求)

     D. 过滤器可以配置全局或者作用于Controller 、Action.

    (2). 是否合法校验思路

     A. 登录成功后,将所需信息存放到PayLoad中,然后进行Jwt加密,将加密字符串返回给客户端,客户端后续请求需要携带该Jwt字符串。

     B. 编写SkipJwt特性用于标记方法跳过登录校验。

     C. 编写过滤器,判断Jwt是否合法、是否过期等,从而决定继续 or 驳回。(区分是否是Ajax请求)

     D. 过滤器可以配置全局或者作用于Controller 、Action.

    2.代码实操

    校验登录过滤器:

     /// <summary>
        /// 校验是否登录的过滤器
        /// </summary>
        public class CheckLogin : Attribute, IAuthorizationFilter
        {
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                //也可以这样获取Session,就不需要注入了。
                var _session = context.HttpContext.Session;
                //判断action是否有skip特性
                #region 老写法
                {
                    //bool isHasAttr = false;
                    ////目标对象上所有特性
                    //var data = context.ActionDescriptor.EndpointMetadata.ToList();
                    //string attrName = typeof(SkipAttribute).ToString();
                    ////循环比对是否含有skip特性
                    //for (int i = 0; i < data.Count; i++)
                    //{
                    //    if (data[i].ToString().Equals(attrName))
                    //    {
                    //        isHasAttr = true;
                    //        break;
                    //    }
                    //}
                }
                #endregion
    
                var isHasAttr = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAllAttribute)|| x.GetType() == typeof(SkipLoginAttribute));
                if (isHasAttr == false)   //表示需要校验,反之不需要校验,正常走业务
                {
                    //判断是否登录
                    var userId = _session.GetString("userId");
                    if (string.IsNullOrEmpty(userId))
                    {
                        //表示没有值,校验没有通过
                        //判断请求类型
                        if (IsAjaxRequest(context.HttpContext.Request))
                        {
                            //表示是ajax请求
                            //context.Result = new JsonResult(new { status = "error", msg = "您没有登录" });
                            context.Result = new ContentResult() { StatusCode = 401, Content = "您没有登录" };
                            return;
                        }
                        else
                        {
                            context.Result = new RedirectResult("/Admin/ErrorIndex?isLogin=noLogin");
                            return;
                        }
                    }
                }
            }
    
            /// <summary>
            /// 判断该请求是否是ajax请求
            /// </summary>
            /// <param name="request"></param>
            /// <returns></returns>
            private bool IsAjaxRequest(HttpRequest request)
            {
                string header = request.Headers["X-Requested-With"];
                return "XMLHttpRequest".Equals(header);
            }
        }
    View Code

    校验jwt的过滤器

    /// <summary>
        /// JWT校验过滤器
        /// </summary>
        public class CheckJWT : ActionFilterAttribute
        {
            private IConfiguration _configuration;
            public CheckJWT(IConfiguration configuration)
            {
                _configuration = configuration;
            }
            public override void OnActionExecuting(ActionExecutingContext context)
            {        
                //判断action是否有skip特性
                #region 老写法
                {
                    //bool isHasAttr = false;
                  ////目标对象上所有特性
                  //var data = context.ActionDescriptor.EndpointMetadata.ToList();
                  //string attrName = typeof(SkipAttribute).ToString();
                  ////循环比对是否含有skip特性
                  //for (int i = 0; i < data.Count; i++)
                  //{
                  //    if (data[i].ToString().Equals(attrName))
                  //    {
                  //        isHasAttr = true;
                  //        break;
                  //    }
                  //}
                }
                #endregion
    
                
                var isHasAttr = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAllAttribute) || x.GetType() == typeof(SkipJwtAttribute));
                if (isHasAttr==false)   //表示需要校验,反之不需要校验,正常走业务   
                {
                    var actionContext = context.HttpContext;
                    if (IsAjaxRequest(actionContext.Request))
                    {
                        //表示是ajax请求,则auth从Header中传过来
                        var token = actionContext.Request.Headers["auth"].ToString();
                        if (token == "null" || string.IsNullOrEmpty(token))
                        {
                            //context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数为空" });
                            context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数为空" };
                            return;
                        }
                        //校验auth的正确性
                        var result = JWTHelp.JWTJieM(token, _configuration["JWTSecret"]);
                        if (result == "expired")
                        {
                            //context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数已经过期" });
                            context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数已经过期" };
                            return;
                        }
                        else if (result == "invalid")
                        {
                            //context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                            context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" };
                            return;
                        }
                        else if (result == "error")
                        {
                            //context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                            context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" };
                            return;
    
                        }
                        else
                        {
                            //表示校验通过,用于向控制器中传值
                            context.RouteData.Values.Add("auth", result);
                        }
    
                    }
                    else
                    {
                        //表示是非ajax请求,则auth拼接在参数中传过来
                        var token = actionContext.Request.Query["auth"].ToString();
                        if (string.IsNullOrEmpty(token))
                        {
                            context.Result = new RedirectResult("/Admin/ErrorIndex?isLogin=noPer");
                            return;
                        }
                        //校验auth的正确性
                        var result = JWTHelp.JWTJieM(token, _configuration["JWTSecret"]);
                        if (result == "expired")
                        {
                            context.Result = new RedirectResult("/Admin/ErrorIndex?isLogin=noPer");
                            return;
                        }
                        else if (result == "invalid")
                        {
                            context.Result = new RedirectResult("/Admin/ErrorIndex?isLogin=noPer");
                            return;
                        }
                        else if (result == "error")
                        {
                            context.Result = new RedirectResult("/Admin/ErrorIndex?isLogin=noPer");
                            return;
                        }
                        else
                        {
                            //表示校验通过,用于向控制器中传值
                            context.RouteData.Values.Add("auth", result);
                        }
                    }
                }
            }
            /// <summary>
            /// 判断该请求是否是ajax请求
            /// </summary>
            /// <param name="request"></param>
            /// <returns></returns>
            private bool IsAjaxRequest(HttpRequest request)
            {
                string header = request.Headers["X-Requested-With"];
                return "XMLHttpRequest".Equals(header);
            }
        }
    View Code

    3个跨过校验的特性标签

        /// <summary>
        /// 跨过系统所有校验
        /// </summary>
        public class SkipAllAttribute : Attribute
        {
        }
        /// <summary>
        /// 跨过JWT校验
        /// </summary>
        public class SkipJwtAttribute : Attribute
        {
        }
         /// <summary>
        /// 跨过登录校验
        /// </summary>
        public class SkipLoginAttribute : Attribute
        {
        }
    View Code

    登录业务 

            /// <summary>
            /// 校验登录
            /// </summary>
            /// <param name="userAccount">账号</param>
            /// <param name="passWord">密码</param>
            /// <returns></returns>
            [SkipAll]
            public IActionResult CheckLogin(string userAccount, string passWord)
            {
                try
                {
                    //1.校验账号是否存在
                    var userInfor = _baseService.Entities<T_SysUser>().Where(u => u.userAccount == userAccount).FirstOrDefault();
                    if (userInfor != null)
                    {
                        //2. 账号和密码是否匹配
                        var passWord1 = SecurityHelp.SHA(passWord);
                        if (passWord1.Equals(userInfor.userPwd, StringComparison.InvariantCultureIgnoreCase))
                        {
                            //3. 存入缓存
                            HttpContext.Session.SetString("userId", userInfor.id);
                            //4. 产生token进行返回
                            //过期时间(可以不设置,下面表示签名后 12个小时过期)
                            double exp = (DateTime.UtcNow.AddHours(12) - new DateTime(1970, 1, 1)).TotalSeconds;
                            //进行组装
                            var payload = new Dictionary<string, object>
                            {
                                 {"userId", userInfor.id },
                                 {"userAccount", userInfor.userAccount },
                                 {"exp",exp }
                             };
                            var token = JWTHelp.JWTJiaM(payload, _configuration["JWTSecret"]);
                            //5.记录登录日志
                            T_SysLoginLog sysLoginLog = new T_SysLoginLog()
                            {
                                id = Guid.NewGuid().ToString("N"),
                                userId = userInfor.id,
                                userAccount = userInfor.userAccount,
                                loginTime = DateTime.Now,
                                delFlag = 0,
                                loginIp = HttpContext.Connection.RemoteIpAddress.ToString()
                            };
                            _baseService.Add(sysLoginLog);
    
                            return Json(new { status = "ok", msg = "登录成功", data = token });
                        }
                        else
                        {
                            //密码不正确
                            return Json(new { status = "error", msg = "密码不正确", data = "" });
                        }
                    }
                    else
                    {
                        return Json(new { status = "error", msg = "账号不存在", data = "" });
                    };
                }
                catch (Exception ex)
                {
                    LogUtils.Error(ex); ;
                    return Json(new { status = "error", msg = "登录失败", data = "" });
                }
    
            }
    View Code

    其它关于如何传值的问题,详见开篇的参考文章。

     

    三. 自定义黑名单

    1. 目的

     这里我们的主要目的是学习自定义中间件的写法,借助IP黑名单这个场景进行落实。

    2. 实操

    (1). 中间件代码

     /// <summary>
        /// 非法ip拦截中间件
        /// </summary>
        public class SafeIpMiddleware
        {
            private readonly RequestDelegate _next;
            private readonly string _illegalIpList;
            public SafeIpMiddleware(RequestDelegate next, string IllegalIpList)
            {
                _illegalIpList = IllegalIpList;
                _next = next;
            }
            public async Task Invoke(HttpContext context)
            {
                if (context.Request.Method == "GET"|| context.Request.Method == "POST")
                {
                    var remoteIp = context.Connection.RemoteIpAddress;    //获取远程访问IP
                    string[] ip = _illegalIpList.Split(';');
                    var bytes = remoteIp.GetAddressBytes();
                    var badIp = false;
                    foreach (var address in ip)
                    {
                        var testIp = IPAddress.Parse(address);
                        if (testIp.GetAddressBytes().SequenceEqual(bytes))
                        {
                            badIp = true;
                            break;    //直接跳出ForEach循环
                        }
                    }
                    if (badIp)
                    {
                        context.Response.StatusCode = 401;
                        return;
                    }
                }
                await _next.Invoke(context);
            }
        }
    View Code

    (2). Configure中注入 

     //6. 自定义中间件,拦截非法ip
      app.UseMiddleware<SafeIpMiddleware>(_Configuration["IllegalIp"]);

    (3). 配置文件 

      //非法ip集合
      "IllegalIp": "192.168.1.100;22.535.22.85",

    四. 日志

     参考:

      Log4Net:https://www.cnblogs.com/yaopengfei/p/9428206.html   https://www.cnblogs.com/yaopengfei/p/10864412.html

      SeriLog:https://www.cnblogs.com/yaopengfei/p/14261414.html

    PS:该框架后期日志将以SeriLog为主,逐步淘汰Log4Net。

    1. Log4Net

    (1). 帮助类

        /// <summary>
        /// 日志帮助类
        /// 依赖程序集:【log4net】
        /// </summary>
        public class LogUtils2
        {
            //日志仓储(单例模式,静态变量,程序在第一次使用的时候被调用,由clr保证)
            private static ILoggerRepository loggerRepository;
            //1. 适用于全部文件夹 (暂时不启用)
            public static ILog log;
            //2. OneLog文件夹
            public static ILog log1;
            //3. TwoLog文件夹
            public static ILog log2;
    
            //声明文件夹名称(这里分两个文件夹)
            static string log1Name = "WebLog";
            static string log2Name = "ApiLog";
    
            /// <summary>
            /// 初始化Log4net的配置
            /// xml文件一定要改为嵌入的资源
            /// </summary>
            public static void InitLog()
            {
                //1. 创建日志仓储(单例)
                loggerRepository = loggerRepository ?? LogManager.CreateRepository("myLog4net");
                //2. 加载xml文件
                Assembly assembly = Assembly.GetExecutingAssembly();
                //路径
                var xml = assembly.GetManifestResourceStream("YpfCore.Utils.Log.Log4net.log4net.xml");
                log4net.Config.XmlConfigurator.Configure(loggerRepository, xml);
                //3. 创建日志对象
                log = LogManager.GetLogger(loggerRepository.Name, "all");
                log1 = LogManager.GetLogger(loggerRepository.Name, log1Name);
                log2 = LogManager.GetLogger(loggerRepository.Name, log2Name);
    
            }
    
    
            /************************* 五种不同日志级别 *******************************/
            //FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息)
    
            #region 00-将调试的信息输出,可以定位到具体的位置(解决高层封装带来的问题)
            /// <summary>
            /// 将调试的信息输出,可以定位到具体的位置(解决高层封装带来的问题)
            /// </summary>
            /// <returns></returns>
            private static string getDebugInfo()
            {
                StackTrace trace = new StackTrace(true);
                return trace.ToString();
            }
            #endregion
    
            #region 01-DEBUG(调试信息)
            /// <summary>
            /// DEBUG(调试信息)
            /// </summary>
            /// <param name="msg">日志信息</param>
            ///  <param name="logName">文件夹名称</param>
            public static void Debug(string msg, string logName = "")
            {
                if (logName == "")
                {
                    log1.Debug(getDebugInfo() + msg);
                }
                else if (logName == log1Name)
                {
                    log1.Debug(getDebugInfo() + msg);
                }
                else if (logName == log2Name)
                {
                    log2.Debug(getDebugInfo() + msg);
                }
            }
            /// <summary>
            /// Debug
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="exception">错误信息</param>
            public static void Debug(string msg, Exception exception)
            {
                log.Debug(getDebugInfo() + msg, exception);
            }
    
            #endregion
    
            #region 02-INFO(一般信息)
            /// <summary>
            /// INFO(一般信息)
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="logName">文件夹名称</param>
            public static void Info(string msg, string logName = "")
            {
                if (logName == "")
                {
                    log1.Info(msg);
                }
                else if (logName == log1Name)
                {
                    log1.Info(msg);
                }
                else if (logName == log2Name)
                {
                    log2.Info(msg);
                }
            }
            /// <summary>
            /// Info
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="exception">错误信息</param>
            public static void Info(string msg, Exception exception)
            {
                log.Info(getDebugInfo() + msg, exception);
            }
            #endregion
    
            #region 03-WARN(警告)
            /// <summary>
            ///WARN(警告)
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="logName">文件夹名称</param>
            public static void Warn(string msg, string logName = "")
            {
                if (logName == "")
                {
                    log1.Warn(getDebugInfo() + msg);
                }
                else if (logName == log1Name)
                {
                    log1.Warn(getDebugInfo() + msg);
                }
                else if (logName == log2Name)
                {
                    log2.Warn(getDebugInfo() + msg);
                }
            }
            /// <summary>
            /// Warn
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="exception">错误信息</param>
            public static void Warn(string msg, Exception exception)
            {
                log.Warn(getDebugInfo() + msg, exception);
            }
            #endregion
    
            #region 04-ERROR(一般错误)
            /// <summary>
            /// ERROR(一般错误)
            /// </summary>
            /// <param name="ex">异常日志</param>
            /// <param name="logName">文件夹名称</param>
            public static void Error(Exception ex, string logName = "")
            {
                if (logName == "")
                {
                    log1.Error(getDebugInfo() + ex.Message);
                }
                else if (logName == log1Name)
                {
                    log1.Error(getDebugInfo() + ex.Message);
                }
                else if (logName == log2Name)
                {
                    log2.Error(getDebugInfo() + ex.Message);
                }
            }
            /// <summary>
            /// Error
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="exception">错误信息</param>
            public static void Error(string msg, Exception exception)
            {
                log.Error(getDebugInfo() + msg, exception);
            }
            #endregion
    
            #region 05-FATAL(致命错误)
            /// <summary>
            /// FATAL(致命错误)
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="logName">文件夹名称</param>
            public static void Fatal(string msg, string logName = "")
            {
                if (logName == "")
                {
                    log1.Fatal(getDebugInfo() + msg);
                }
                else if (logName == log1Name)
                {
                    log1.Fatal(getDebugInfo() + msg);
                }
                else if (logName == log2Name)
                {
                    log2.Fatal(getDebugInfo() + msg);
                }
            }
            /// <summary>
            /// Fatal
            /// </summary>
            /// <param name="msg">日志信息</param>
            /// <param name="exception">错误信息</param>
            public static void Fatal(string msg, Exception exception)
            {
                log.Fatal(getDebugInfo() + msg, exception);
            }
    
            #endregion
    
    
        }
    View Code

    (2). 配置文件(要改成嵌入的资源)

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <!-- 一. 添加log4net的自定义配置节点-->
      <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
      </configSections>
      <!--二. log4net的核心配置代码-->
      <log4net>
        <!--1. 输出途径(一) 将日志以回滚文件的形式写到文件中-->
        
        <!--模式一:全部存放到一个文件夹里-->
        <appender name="log0" type="log4net.Appender.RollingFileAppender">
          <!--1.1 文件夹的位置(也可以写相对路径)-->
          <param name="File"  value="D:CoreLog" />
          <!--相对路径-->
          <!--<param name="File"  value="Logs/" />-->
          <!--1.2 是否追加到文件-->
          <param name="AppendToFile" value="true" />
          <!--1.3 使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件 -->
          <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
          <!--1.4 配置Unicode编码-->
          <Encoding value="UTF-8" />
          <!--1.5 是否只写到一个文件里-->
          <param name="StaticLogFileName" value="false" />
          <!--1.6 配置按照何种方式产生多个日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
          <param name="RollingStyle" value="Composite" />
          <!--1.7 介绍多种日志的的命名和存放在磁盘的形式-->
          <!--1.7.1 在根目录下直接以日期命名txt文件 注意&quot;的位置,去空格 -->
          <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
          <!--1.7.2 在根目录下按日期产生文件夹,文件名固定 test.log  -->
          <!--<param name="DatePattern" value="yyyy-MM-dd/&quot;test.log&quot;"  />-->
          <!--1.7.3 在根目录下按日期产生文件夹,这是按日期产生文件夹,并在文件名前也加上日期  -->
          <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd&quot;-test.log&quot;"  />-->
          <!--1.7.4 在根目录下按日期产生文件夹,这再形成下一级固定的文件夹  -->
          <!--<param name="DatePattern" value="yyyyMMdd/&quot;OrderInfor/test.log&quot;"  />-->
          <!--1.8 配置每个日志的大小。【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】可用的单位:KB|MB|GB。不要使用小数,否则会一直写入当前日志,
          超出大小后在所有文件名后自动增加正整数重新命名,数字最大的最早写入。-->
          <param name="maximumFileSize" value="10MB" />
          <!--1.9 最多产生的日志文件个数,超过则保留最新的n个 将value的值设置-1,则不限文件个数 【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】
            与1.8中maximumFileSize文件大小是配合使用的-->
          <param name="MaxSizeRollBackups" value="5" />
          <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定义布局-->
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n%newline"/>
          </layout>
        </appender>
    
        <!--模式二:分文件夹存放-->
        <!--文件夹1-->
        <appender name="log1" type="log4net.Appender.RollingFileAppender">
          <!--<param name="File"  value="D:CoreLogOneLog" />-->
          <!--改成存放到项目目录下了-->
          <param name="File"  value="Log/WebLog/" />
          <param name="AppendToFile" value="true" />
          <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
          <Encoding value="UTF-8" />
          <param name="StaticLogFileName" value="false" />
          <param name="RollingStyle" value="Composite" />
          <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
          <param name="maximumFileSize" value="10MB" />
          <param name="MaxSizeRollBackups" value="5" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level %n日志内容:%message%newline %n%newline"/>
          </layout>
          <!--下面是利用过滤器进行分文件夹存放,两种过滤器进行配合-->
          <!--与Logger名称(OneLog)匹配,才记录,-->
          <filter type="log4net.Filter.LoggerMatchFilter">
            <loggerToMatch value="WebLog" />
          </filter>
          <!--阻止所有的日志事件被记录-->
          <filter type="log4net.Filter.DenyAllFilter" />
        </appender>
        <!--文件夹2-->
        <appender name="log2" type="log4net.Appender.RollingFileAppender">
          <!--<param name="File"  value="D:CoreLogTwoLog" />-->
          <!--改成存放到项目目录下了-->
          <param name="File"  value="Log/ApiLog/" />
          <param name="AppendToFile" value="true" />
          <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
          <Encoding value="UTF-8" />
          <param name="StaticLogFileName" value="false" />
          <param name="RollingStyle" value="Composite" />
          <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
          <param name="maximumFileSize" value="10MB" />
          <param name="MaxSizeRollBackups" value="5" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level  %n日志内容:%message%newline %n%newline"/>
          </layout>
          <!--下面是利用过滤器进行分文件夹存放,两种过滤器进行配合-->
          <!--与Logger名称(TwoLog)匹配,才记录,-->
          <filter type="log4net.Filter.LoggerMatchFilter">
            <loggerToMatch value="ApiLog" />
          </filter>
          <!--阻止所有的日志事件被记录-->
          <filter type="log4net.Filter.DenyAllFilter" />
        </appender>
    
    
        <!--2. 输出途径(二) 记录日志到数据库-->
        <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
          <!--2.1 设置缓冲区大小,只有日志记录超设定值才会一块写入到数据库-->
          <param name="BufferSize" value="1" />
          <!--2.2 引用-->
          <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          <!--2.3 数据库连接字符串-->
          <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
          <!--2.4 SQL语句插入到指定表-->
          <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
          <!--2.5 数据库字段匹配-->
          <!-- 线程号-->
          <parameter>
            <parameterName value="@threadId" />
            <dbType value="String" />
            <size value="100" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%thread" />
            </layout>
          </parameter>
          <!--日志级别-->
          <parameter>
            <parameterName value="@log_level" />
            <dbType value="String" />
            <size value="100" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%level" />
            </layout>
          </parameter>
          <!--日志记录类名称-->
          <parameter>
            <parameterName value="@log_name" />
            <dbType value="String" />
            <size value="100" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%logger" />
            </layout>
          </parameter>
          <!--日志信息-->
          <parameter>
            <parameterName value="@log_msg" />
            <dbType value="String" />
            <size value="5000" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%message" />
            </layout>
          </parameter>
          <!--异常信息  指的是如Infor 方法的第二个参数的值-->
          <parameter>
            <parameterName value="@log_exception" />
            <dbType value="String" />
            <size value="2000" />
            <layout type="log4net.Layout.ExceptionLayout" />
          </parameter>
          <!-- 日志记录时间-->
          <parameter>
            <parameterName value="@log_time" />
            <dbType value="DateTime" />
            <layout type="log4net.Layout.RawTimeStampLayout" />
          </parameter>
        </appender>
    
    
        <!--(二). 配置日志的的输出级别和加载日志的输出途径-->
        <root>
          <!--1. level中的value值表示该值及其以上的日志级别才会输出-->
          <!--OFF > FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息)  > ALL  -->
          <!--OFF表示所有信息都不写入,ALL表示所有信息都写入-->
          <level value="ALL"></level>
          <!--2. append-ref标签表示要加载前面的日志输出途径代码  通过ref和appender标签的中name属性相关联-->
          
          <!--<appender-ref ref="AdoNetAppender"></appender-ref>-->
          <!--<appender-ref ref="log0"></appender-ref>-->
          <appender-ref ref="log1"></appender-ref>
          <appender-ref ref="log2"></appender-ref>
        </root>
      </log4net>
    
    </configuration>
    View Code

    2. SeriLog

     给YpfCore.Utils层添加程序集【Serilog】【Serilog.Sinks.File】【Serilog.Sinks.Async】,然后封装LogUtils类,利用Filter过滤器实现分文件夹存储,在ConfigureService中进行初始化。这里仅封装一个Infor和Error方法,其它可自行封装。

    代码分享:

        /// <summary>
        /// SeriLog帮助类
        /// </summary>
        public class LogUtils
        {
            static string log1Name = "WebLog";
            static string log2Name = "ApiLog";
            static string log3Name = "ErrorWebLog";
            static string log4Name = "ErrorApiLog";
    
            /// <summary>
            /// 初始化日志
            /// </summary>
            public static void InitLog()
            {
                //static string LogFilePath(string FileName) => $@"{AppContext.BaseDirectory}Log{FileName}log.log";   //bin目录下
                static string LogFilePath(string FileName) => $@"Log{FileName}log.log";
                string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100);
                Serilog.Log.Logger = new LoggerConfiguration()
                            .Enrich.FromLogContext()
                            .MinimumLevel.Debug() // 所有Sink的最小记录级别
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log1Name)).WriteTo.Async(a => a.File(LogFilePath(log1Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log2Name)).WriteTo.Async(a => a.File(LogFilePath(log2Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log3Name)).WriteTo.Async(a => a.File(LogFilePath(log3Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log4Name)).WriteTo.Async(a => a.File(LogFilePath(log4Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                            .CreateLogger();
            }
    
    
            /*****************************下面是不同日志级别*********************************************/
            // FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部)
    
    
            /// <summary>
            /// 普通日志
            /// </summary>
            /// <param name="msg">日志内容</param>
            /// <param name="fileName">文件夹名称</param>
            public static void Info(string msg, string fileName = "")
            {
                if (fileName == "" || fileName == log1Name)
                {
                    Serilog.Log.Information($"{{position}}:{msg}", log1Name);
                }
                else if (fileName == log2Name)
                {
                    Serilog.Log.Information($"{{position}}:{msg}", log2Name);
                }
                else
                {
                    //输入其他的话,还是存放到第一个文件夹
                    Serilog.Log.Information($"{{position}}:{msg}", log1Name);
                }
            }
    
            /// <summary>
            /// 异常日志
            /// </summary>
            /// <param name="ex">Exception</param>
            /// <param name="fileName">文件夹名称</param>
            public static void Error(Exception ex, string fileName = "")
            {
                if (fileName == "" || fileName == log3Name)
                {
    
                    Serilog.Log.Error(ex, "{position}:" + ex.Message, log3Name);
                }
                else if (fileName == log4Name)
                {
    
                    Serilog.Log.Error(ex, "{position}:" + ex.Message, log4Name);
                }
                else
                {
                    //输入其他的话,还是存放到第一个文件夹
                    Serilog.Log.Error(ex, "{position}:" + ex.Message, log3Name);
                }
            }
        }
    View Code

    代码调用:

                {
                    LogUtils.Info("我是二哈");
                    LogUtils.Info("我是二哈1", "WebLog");//效果同上
                    LogUtils.Info("我是二哈2", "ApiLog");
                    try
                    {
                        int.Parse("dsfsdf");
                    }
                    catch (Exception ex)
                    {
                        LogUtils.Error(ex);
                        LogUtils.Error(ex, "ErrorWebLog");  //效果同上
                    }
                } 

    3. 最终整合 

     后续Log4net将彻底弃用,所以这里暂时不抽象接口来注入了,使用静态方法的模式简单粗暴,仅提供一个简单的策略用于选择使用哪种日志。

    代码分享:

            /// <summary>
            /// 注册日志服务
            /// </summary>
            /// <param name="services"></param>
            /// <returns></returns>
            public static IServiceCollection AddLogStrategy(this IServiceCollection services, string logType = "SeriLog")
            { 
                if (logType == "Log4net")
                {
                    LogUtils2.InitLog();
                }
                else
                {
                    LogUtils.InitLog();
                }
                return services;
            }

    ConfigureService注册:

    //添加日志策略(SeriLog 或 Log4net)
    services.AddLogStrategy(_Configuration["LogType"]);

    配置文件: 

      //日志类型(SeriLog 或 Log4net)
      "LogType": "SeriLog"

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    获取当前页URL函数
    Windows下搭建PHP开发环境
    Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed,SSL报错
    微信接口随笔
    老毛桃安装系统,最正确的快速的思路和办法
    老毛桃制作U盘启动盘
    十进制与三十六机制 互转 PHP代码
    Linux环境下修改GIT的用户名和邮箱
    Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: htmlParseEntityRef: expecting ';' in Entity,
    如何保证库存数量及时
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/14244416.html
Copyright © 2011-2022 走看看