一. 緩存
參考文章:
(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); } }
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); } }
校驗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); } }
3個跨過校驗的特性標簽

/// <summary> /// 跨過系統所有校驗 /// </summary> public class SkipAllAttribute : Attribute { } /// <summary> /// 跨過JWT校驗 /// </summary> public class SkipJwtAttribute : Attribute { } /// <summary> /// 跨過登錄校驗 /// </summary> public class SkipLoginAttribute : Attribute { }
登錄業務

/// <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 = "" }); } }
其它關於如何傳值的問題,詳見開篇的參考文章。
三. 自定義黑名單
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); } }
(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 }
(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文件 注意"的位置,去空格 --> <param name="DatePattern" value="yyyy-MM-dd".log"" /> <!--1.7.2 在根目錄下按日期產生文件夾,文件名固定 test.log --> <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />--> <!--1.7.3 在根目錄下按日期產生文件夾,這是按日期產生文件夾,並在文件名前也加上日期 --> <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />--> <!--1.7.4 在根目錄下按日期產生文件夾,這再形成下一級固定的文件夾 --> <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />--> <!--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".log"" /> <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".log"" /> <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>
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); } } }
代碼調用:
{ 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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。