第三節:必備中間件集成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:\CoreLog\OneLog\" />-->
      <!--改成存放到項目目錄下了-->
      <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:\CoreLog\TwoLog\" />-->
      <!--改成存放到項目目錄下了-->
      <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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM