別的也很少說沒直接貼代碼html
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Time20180929_DateTime { class Program { static void Main(string[] args) { /* * 1.DateTime(Int64):將 DateTime 結構的新實例初始化爲指定的刻度數。 * 2.DateTime(Int64, DateTimeKind):將 DateTime 結構的新實例初始化爲指定的計時週期數以及協調世界時 (UTC) 或本地時間。 * 3.DateTime(Int32, Int32, Int32):將 DateTime 結構的新實例初始化爲指定的年、月和日。 * 4.DateTime(Int32, Int32, Int32, Calendar):將 DateTime 結構的新實例初始化爲指定日曆的指定年、月和日。 * 5.DateTime(Int32, Int32, Int32, Int32, Int32, Int32):將 DateTime 結構的新實例初始化爲指定的年、月、日、小時、分鐘和秒。 * 6.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, DateTimeKind) :將 DateTime 結構的新實例初始化爲指定年、月、日、小時、分鐘、秒和協調世界時 (UTC) 或本地時間。 * 7.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Calendar):將 DateTime 結構的新實例初始化爲指定日曆的年、月、日、小時、分鐘和秒。 * 8.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32):將 DateTime 結構的新實例初始化爲指定的年、月、日、小時、分鐘、秒和毫秒。 * 9.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, DateTimeKind):將 DateTime 結構的新實例初始化爲指定年、月、日、小時、分鐘、秒、毫秒和協調世界時 (UTC) 或本地時間。 * 10.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar):將 DateTime 結構的新實例初始化爲指定日曆的指定年、月、日、小時、分鐘、秒和毫秒。 * 11.DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar, DateTimeKind):將 DateTime 結構的新實例初始化爲指定日曆的指定年、月、日、小時、分鐘、秒、毫秒和協調世界時 (UTC) 或本地時間。 */ /* * 1.public DateTime (long ticks): * 一個日期和時間,以公曆 0001 年 1 月 1 日 00:00:00.000 以來所經歷的以 100 納秒爲間隔的間隔數來表示。 * ticks 小於 MinValue 或大於 MaxValue。 */ //時間屬性MinValue和MaxValue long _minval = DateTime.MinValue.Ticks; long _maxval = DateTime.MaxValue.Ticks; DateTime _mintime = DateTime.MinValue; DateTime _maxtime = DateTime.MaxValue; string desc = "minval:{0}, _maxval:{1}, _mintime:{2}, _maxtime:{3}"; string format = "{0}) The {1} date and time is {2:MM/dd/yy hh:mm:ss tt}"; DateTime dt1 = new DateTime(DateTime.MinValue.Ticks); DateTime dt2 = new DateTime(DateTime.MaxValue.Ticks); //建立一個傳統時間爲上午2018/9/29 10:39:50基於名稱指定的區域性"en-US"並基於布爾值(指定是否使用系統中用戶選定的區域性設文化ticks long ticks = new DateTime(2018, 9, 29, 10, 39, 50, new CultureInfo("en-US", false).Calendar).Ticks; DateTime dt3 = new DateTime(ticks); Console.WriteLine(desc, _minval, _maxval, _mintime, _maxtime); Console.WriteLine(format, 1, "minimum", dt1); Console.WriteLine(format, 2, "maxmum", dt2); Console.WriteLine(format, 3, "custom ", dt3); Console.WriteLine("\nThe custom date and time is created from {0:N0} ticks.", ticks); Console.WriteLine("\nThe custom date and time is created from {0} ticks.", ticks); /* minval:0, _maxval:3155378975999999999, _mintime:0001/1/1 0:00:00, _maxtime:9999/12/31 23:59:59 1) The minimum date and time is 01/01/01 12:00:00 上午 2) The maxmum date and time is 12/31/99 11:59:59 下午 3) The custom date and time is 09/29/18 10:39:50 上午 The custom date and time is created from 636,738,143,900,000,000 ticks. The custom date and time is created from 636738143900000000 ticks. */ /* * 2.public DateTime (long ticks, DateTimeKind kind); * DateTime(Int64, DateTimeKind):將 DateTime 結構的新實例初始化爲指定的計時週期數以及協調世界時 (UTC) 或本地時間。 * kind: * Unspecified 0 表示的時間既未指定爲本地時間,也未指定爲協調通用時間 (UTC)。 * Utc 1 表示的時間爲 UTC。 * Local 2 表示的時間爲本地時間 */ long ticks2 = new DateTime(2018, 9, 29, 10, 39, 50, new CultureInfo("en-US", false).Calendar).Ticks; Console.WriteLine("2018-09-29 10:39:50"); Console.WriteLine("Unspecified:{0}", new DateTime(ticks2, DateTimeKind.Unspecified)); Console.WriteLine("Utc:{0}", new DateTime(ticks2, DateTimeKind.Utc)); Console.WriteLine("Local:{0}", new DateTime(ticks2, DateTimeKind.Local)); /* 2018-09-29 10:39:50 Unspecified:2018/9/29 10:39:50 Utc:2018/9/29 10:39:50 Local:2018/9/29 10:39:50 */ /* * 3.public DateTime (int year, int month, int day); * DateTime(Int32, Int32, Int32) * 將 DateTime 結構的新實例初始化爲指定的年、月和日。 * year:1-9999,month:0-12,day:1-moth中的天數 */ DateTime dt4 = new DateTime(2018, 9, 18); Console.WriteLine("dt4的值爲{0}", dt4.ToString()); /* dt4的值爲2018/9/18 0:00:00 */ /* * 4.public DateTime (int year, int month, int day, System.Globalization.Calendar calendar); * DateTime(Int32, Int32, Int32, Calendar):將 DateTime 結構的新實例初始化爲指定日曆的指定年、月和日。 * year:1-9999,month:0-12,day:1-moth中的天數,Calendar用於解釋 year、month 和 day 的日曆。 */ /* 下面的示例調用DateTime(Int32, Int32, Int32, Calendar)構造函數兩次實例化兩個DateTime值。 第一次調用實例化DateTime經過使用值PersianCalendar對象。 因爲波斯日曆不能指定爲區域性的默認日曆,顯示日期與波斯歷須要單獨調用其PersianCalendar.GetMonth, PersianCalendar.GetDayOfMonth,和PersianCalendar.GetYear方法。 構造函數的第二個調用實例化DateTime經過使用值HijriCalendar對象。 該示例將當前區域性更改成阿拉伯語 (敘利亞) 和當前區域性的默認日曆更改成回曆。 由於回曆是當前區域性的默認日曆,Console.WriteLine方法使用它來設置日期格式。 還原先前的當前區域性 (這在此狀況下是英語 (美國)) 時,Console.WriteLine方法使用當前區域性的默認公曆日從來設置日期格式。 */ Console.WriteLine("Using the Persian Calendar:"); PersianCalendar persian = new PersianCalendar(); DateTime dt5 = new DateTime(2018,9,29, persian); Console.WriteLine(dt5.ToString()); Console.WriteLine("{0}/{1}/{2}\n", persian.GetMonth(dt5),persian.GetDayOfMonth(dt5),persian.GetYear(dt5)); Console.WriteLine("Using the Hijri Calendar:"); CultureInfo dftCulture = Thread.CurrentThread.CurrentCulture; // Define Hijri calendar. HijriCalendar hijri = new HijriCalendar(); // Make ar-SY the current culture and Hijri the current calendar. Thread.CurrentThread.CurrentCulture = new CultureInfo("ar-SY"); CultureInfo current = CultureInfo.CurrentCulture; current.DateTimeFormat.Calendar = hijri; string dFormat = current.DateTimeFormat.ShortDatePattern; // Ensure year is displayed as four digits. dFormat = Regex.Replace(dFormat, "/yy$", "/yyyy"); current.DateTimeFormat.ShortDatePattern = dFormat; DateTime date2 = new DateTime(2018,9,29, hijri); Console.WriteLine("{0} culture using the {1} calendar: {2:d}", current,GetCalendarName(hijri), date2); // Restore previous culture. Thread.CurrentThread.CurrentCulture = dftCulture; Console.WriteLine("{0} culture using the {1} calendar: {2:d}",CultureInfo.CurrentCulture,GetCalendarName(CultureInfo.CurrentCulture.Calendar),date2); /* Using the Persian Calendar: 2639/12/20 0:00:00 9/29/2018 Using the Hijri Calendar: ar-SY culture using the Hijri calendar: 29/09/2018 zh-CN culture using the Gregorian calendar: 2580/3/16 */ /* * 5.public DateTime (int year, int month, int day, int hour, int minute, int second); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32) * 將 DateTime 結構的新實例初始化爲指定的年、月、日、小時、分鐘和秒。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59 */ DateTime date1 = new DateTime(2018, 9, 29, 11, 20, 26); Console.WriteLine(date1.ToString()); /*2018/9/29 11:20:26*/ /* * 6.public DateTime (int year, int month, int day, int hour, int minute, int second, DateTimeKind kind); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, DateTimeKind) * 將 DateTime 結構的新實例初始化爲指定年、月、日、小時、分鐘、秒和協調世界時 (UTC) 或本地時間。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59 *kind: *Unspecified 0 表示的時間既未指定爲本地時間,也未指定爲協調通用時間 (UTC)。 *Utc 1 表示的時間爲 UTC。 *Local 2 表示的時間爲本地時間 */ DateTime date9 = new DateTime(2010, 8, 18, 16, 32, 0, DateTimeKind.Local); Console.WriteLine("日期:{0}\nDateTimeKind:{1}", date9, date9.Kind); /* 日期:2010/8/18 16:32:00 DateTimeKind:Local */ /* *7.public DateTime (int year, int month, int day, int hour, int minute, int second, System.Globalization.Calendar calendar); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Calendar) * 將 DateTime 結構的新實例初始化爲指定日曆的年、月、日、小時、分鐘和秒。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59, * calendar:用於解釋 year、month 和 day 的日曆。 */ /* *8.public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32) * 將 DateTime 結構的新實例初始化爲指定的年、月、日、小時、分鐘、秒和毫秒。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59,millisecond:0-999 */ DateTime date3 = new DateTime(2018, 9, 29, 13, 44, 30, 555); Console.WriteLine(date3.ToString("MM/dd/yyyy HH:mm:ss.fff tt")); /*09/29/2018 13:44:30.555 下午*/ /* * 9.public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, DateTimeKind) * 將 DateTime 結構的新實例初始化爲指定年、月、日、小時、分鐘、秒、毫秒和協調世界時 (UTC) 或本地時間。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59,millisecond:0-999 * kind:枚舉值之一,該值指示 year、month、day、hour、minute、second 和 millisecond 指定了本地時間、 * 協調世界時 (UTC),仍是二者皆未指定。 */ DateTime date4 = new DateTime(2018, 9, 29, 13, 48, 30, 500, DateTimeKind.Local); Console.WriteLine("{0:M/dd/yyyy h:mm:ss.fff tt} {1}", date4, date4.Kind); /* * 9/29/2018 1:48:30.500 下午 Local */ /* * 10.public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, System.Globalization.Calendar calendar); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar) * 將 DateTime 結構的新實例初始化爲指定日曆的指定年、月、日、小時、分鐘、秒和毫秒。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59,millisecond:0-999 * calendar:用於解釋 year、month 和 day 的日曆 */ /* 下面的示例調用DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar)構造函數兩次實例化兩個DateTime值。 第一次調用實例化DateTime經過使用值PersianCalendar對象。 因爲波斯日曆不能指定爲區域性的默認日曆,顯示日期與波斯歷須要單獨調用其PersianCalendar.GetMonth, PersianCalendar.GetDayOfMonth,和PersianCalendar.GetYear方法。 構造函數的第二個調用實例化DateTime經過使用值HijriCalendar對象。 該示例將當前區域性更改成阿拉伯語 (敘利亞) 和當前區域性的默認日曆更改成回曆。 由於回曆是當前區域性的默認日曆,Console.WriteLine方法使用它來設置日期格式。 還原先前的當前區域性 (這在此狀況下是英語 (美國)) 時,Console.WriteLine方法使用當前區域性的默認公曆日從來設置日期格式。 */ Console.WriteLine("Using the Persian Calendar:"); PersianCalendar persian2 = new PersianCalendar(); DateTime date5 = new DateTime(1397, 3, 29, 16, 32, 18, 500, persian2); Console.WriteLine(date5.ToString("M/dd/yyyy h:mm:ss.fff tt")); Console.WriteLine("{0}/{1}/{2} {3}{7}{4:D2}{7}{5:D2}.{6:G3}\n", persian2.GetMonth(date5), persian2.GetDayOfMonth(date5), persian2.GetYear(date5), persian2.GetHour(date5), persian2.GetMinute(date5), persian2.GetSecond(date5), persian2.GetMilliseconds(date5), DateTimeFormatInfo.CurrentInfo.TimeSeparator); Console.WriteLine("Using the Hijri Calendar:"); // Get current culture so it can later be restored. CultureInfo dftCulture2 = Thread.CurrentThread.CurrentCulture; // Define strings for use in composite formatting. string dFormat2; string fmtString; // Define Hijri calendar. HijriCalendar hijri2 = new HijriCalendar(); // Make ar-SY the current culture and Hijri the current calendar. Thread.CurrentThread.CurrentCulture = new CultureInfo("ar-SY"); CultureInfo current2 = CultureInfo.CurrentCulture; current2.DateTimeFormat.Calendar = hijri2; dFormat2 = current.DateTimeFormat.ShortDatePattern; // Ensure year is displayed as four digits. dFormat2 = Regex.Replace(dFormat2, "/yy$", "/yyyy") + " H:mm:ss.fff"; fmtString = "{0} culture using the {1} calendar: {2:" + dFormat2 + "}"; DateTime date6 = new DateTime(1431, 9, 9, 16, 32, 18, 500, hijri2); Console.WriteLine(fmtString, current2, GetCalendarName(hijri2), date6); // Restore previous culture. Thread.CurrentThread.CurrentCulture = dftCulture; dFormat2 = DateTimeFormatInfo.CurrentInfo.ShortDatePattern + " H:mm:ss.fff"; fmtString = "{0} culture using the {1} calendar: {2:" + dFormat2 + "}"; Console.WriteLine(fmtString, CultureInfo.CurrentCulture, GetCalendarName(CultureInfo.CurrentCulture.Calendar), date6); /* Using the Persian Calendar: 6/19/2018 4:32:18.500 下午 3/29/1397 16:32:18.500 Using the Hijri Calendar: ar-SY culture using the Hijri calendar: 09/09/1431 16:32:18.500 zh-CN culture using the Gregorian calendar: 2010/8/18 16:32:18.500 */ /* * 11.public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, System.Globalization.Calendar calendar, DateTimeKind kind); * DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar, DateTimeKind) * 將 DateTime 結構的新實例初始化爲指定日曆的指定年、月、日、小時、分鐘、秒、毫秒和協調世界時 (UTC) 或本地時間。 * year:1-9999,month:0-12,day:1-moth中的天數,hour:0-23,minuye:0-59,second:0-59,millisecond:0-999 * calendar:用於解釋 year、month 和 day 的日曆 * kind:枚舉值之一,該值指示 year、month、day、hour、minute、second 和 millisecond 指定了本地時間、 * 協調世界時 (UTC),仍是二者皆未指定。 */ /* 下面的示例調用DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar, DateTimeKind)構造函數兩次實例化兩個DateTime值。 第一次調用實例化DateTime經過使用值PersianCalendar對象。 因爲波斯日曆不能指定爲區域性的默認日曆,顯示日期與波斯歷須要單獨調用其PersianCalendar.GetMonth, PersianCalendar.GetDayOfMonth,和PersianCalendar.GetYear方法。 構造函數的第二個調用實例化DateTime經過使用值HijriCalendar對象。 該示例將當前區域性更改成阿拉伯語 (敘利亞) 和當前區域性的默認日曆更改成回曆。 由於回曆是當前區域性的默認日曆,Console.WriteLine方法使用它來設置日期格式。 還原先前的當前區域性 (這在此狀況下是英語 (美國)) 時,Console.WriteLine方法使用當前區域性的默認公曆日從來設置日期格式。 */ Console.WriteLine("Using the Persian Calendar:"); PersianCalendar persian3 = new PersianCalendar(); DateTime date7 = new DateTime(1397, 3, 29, 16, 32, 18, 500, persian3, DateTimeKind.Local); Console.WriteLine("{0:M/dd/yyyy h:mm:ss.fff tt} {1}", date7, date7.Kind); Console.WriteLine("{0}/{1}/{2} {3}{8}{4:D2}{8}{5:D2}.{6:G3} {7}\n", persian3.GetMonth(date7), persian3.GetDayOfMonth(date7), persian3.GetYear(date7), persian3.GetHour(date7), persian3.GetMinute(date7), persian3.GetSecond(date7), persian3.GetMilliseconds(date7), date7.Kind, DateTimeFormatInfo.CurrentInfo.TimeSeparator); Console.WriteLine("Using the Hijri Calendar:"); // Get current culture so it can later be restored. CultureInfo dftCulture3 = Thread.CurrentThread.CurrentCulture; // Define strings for use in composite formatting. string dFormat3; string fmtString3; // Define Hijri calendar. HijriCalendar hijri3 = new HijriCalendar(); // Make ar-SY the current culture and Hijri the current calendar. Thread.CurrentThread.CurrentCulture = new CultureInfo("ar-SY"); CultureInfo current3 = CultureInfo.CurrentCulture; current3.DateTimeFormat.Calendar = hijri3; dFormat3 = current3.DateTimeFormat.ShortDatePattern; // Ensure year is displayed as four digits. dFormat3 = Regex.Replace(dFormat3, "/yy$", "/yyyy") + " H:mm:ss.fff"; fmtString3 = "{0} culture using the {1} calendar: {2:" + dFormat3 + "} {3}"; DateTime date8 = new DateTime(1431, 9, 9, 16, 32, 18, 500, hijri3, DateTimeKind.Local); Console.WriteLine(fmtString3, current3, GetCalendarName(hijri3), date8, date8.Kind); // Restore previous culture. Thread.CurrentThread.CurrentCulture = dftCulture3; dFormat3 = DateTimeFormatInfo.CurrentInfo.ShortDatePattern + " H:mm:ss.fff"; fmtString3= "{0} culture using the {1} calendar: {2:" + dFormat3 + "} {3}"; Console.WriteLine(fmtString3, CultureInfo.CurrentCulture, GetCalendarName(CultureInfo.CurrentCulture.Calendar), date8, date8.Kind); /* Using the Persian Calendar: 6/19/2018 4:32:18.500 下午 Local 3/29/1397 16:32:18.500 Local Using the Hijri Calendar: ar-SY culture using the Hijri calendar: 09/09/1431 16:32:18.500 Local zh-CN culture using the Gregorian calendar: 2010/8/18 16:32:18.500 Local */ Console.ReadKey(); } private static string GetCalendarName(Calendar cal) { return Regex.Match(cal.ToString(), "\\.(\\w+)Calendar").Groups[1].Value; } } }
[Abp 源碼分析]十5、自動審計記錄
正文github
0.簡介
Abp 框架爲咱們自帶了審計日誌功能,審計日誌能夠方便地查看每次請求接口所耗的時間,可以幫助咱們快速定位到某些性能有問題的接口。除此以外,審計日誌信息還包含有每次調用接口時客戶端請求的參數信息,客戶端的 IP 與客戶端使用的瀏覽器。有了這些數據以後,咱們就能夠很方便地復現接口產生 BUG 時的一些環境信息。web
固然若是你腦洞更大的話,能夠根據這些數據來開發一個可視化的圖形界面,方便開發與測試人員來快速定位問題。數據庫
PS:c#
若是使用了 Abp.Zero 模塊則自帶的審計記錄實現是存儲到數據庫當中的,可是在使用 EF Core + MySQL(EF Provider 爲 Pomelo.EntityFrameworkCore.MySql) 在高併發的狀況下會有數據庫鏈接超時的問題,這塊推薦是重寫實現,本身採用 Redis 或者其餘存儲方式。windows
若是須要禁用審計日誌功能,則須要在任意模塊的預加載方法(PreInitialize()
) 當中增長以下代碼關閉審計日誌功能。數組
public class XXXStartupModule { public override PreInitialize() { // 禁用審計日誌 Configuration.Auditing.IsEnabled = false; } }
1.啓動流程
審計組件與參數校驗組件同樣,都是經過 MVC 過濾器與 Castle 攔截器來實現記錄的。也就是說,在每次調用接口/方法時都會進入 過濾器/攔截器 並將其寫入到數據庫表 AbpAuditLogs
當中。瀏覽器
其核心思想十分簡單,就是在執行具體接口方法的時候,先使用 StopWatch 對象來記錄執行完一個方法所須要的時間,而且還可以經過 HttpContext
來獲取到一些客戶端的關鍵信息。安全
2.1 過濾器注入
同上一篇文章所講的同樣,過濾器是在 AddAbp()
方法內部的 ConfigureAspNetCore()
方法注入的。
private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver) { // ... 其餘代碼 //Configure MVC services.Configure<MvcOptions>(mvcOptions => { mvcOptions.AddAbp(services); }); // ... 其餘代碼 }
而下面就是過濾器的注入方法:
internal static class AbpMvcOptionsExtensions { public static void AddAbp(this MvcOptions options, IServiceCollection services) { // ... 其餘代碼 AddFilters(options); // ... 其餘代碼 } // ... 其餘代碼 private static void AddFilters(MvcOptions options) { // ... 其餘過濾器注入 // 注入審計日誌過濾器 options.Filters.AddService(typeof(AbpAuditActionFilter)); // ... 其餘過濾器注入 } // ... 其餘代碼 }
2.2 攔截器注入
注入攔截器的地方與 DTO 自動驗證的攔截器的位置同樣,都是在 AbpBootstrapper
對象被構造的時候進行註冊。
public class AbpBootstrapper : IDisposable { private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null) { // ... 其餘代碼 if (!options.DisableAllInterceptors) { AddInterceptorRegistrars(); } } // ... 其餘代碼 // 添加各類攔截器 private void AddInterceptorRegistrars() { ValidationInterceptorRegistrar.Initialize(IocManager); AuditingInterceptorRegistrar.Initialize(IocManager); EntityHistoryInterceptorRegistrar.Initialize(IocManager); UnitOfWorkRegistrar.Initialize(IocManager); AuthorizationInterceptorRegistrar.Initialize(IocManager); } // ... 其餘代碼 }
轉到 AuditingInterceptorRegistrar
的具體實現能夠發現,他在內部針對於審計日誌攔截器的注入是區分了類型的。
internal static class AuditingInterceptorRegistrar { public static void Initialize(IIocManager iocManager) { iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) => { // 若是審計日誌配置類沒有被注入,則直接跳過 if (!iocManager.IsRegistered<IAuditingConfiguration>()) { return; } var auditingConfiguration = iocManager.Resolve<IAuditingConfiguration>(); // 判斷當前 DI 所注入的類型是否應該爲其綁定審計日誌攔截器 if (ShouldIntercept(auditingConfiguration, handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AuditingInterceptor))); } }; } // 本方法主要用於判斷當前類型是否符合綁定攔截器的條件 private static bool ShouldIntercept(IAuditingConfiguration auditingConfiguration, Type type) { // 首先判斷當前類型是否在配置類的註冊類型之中,若是是,則進行攔截器綁定 if (auditingConfiguration.Selectors.Any(selector => selector.Predicate(type))) { return true; } // 當前類型若是擁有 Audited 特性,則進行攔截器綁定 if (type.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true)) { return true; } // 若是當前類型內部的全部方法當中有一個方法擁有 Audited 特性,則進行攔截器綁定 if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true))) { return true; } // 都不知足則返回 false,不對當前類型進行綁定 return false; } }
能夠看到在判斷是否綁定攔截器的時候,Abp 使用了 auditingConfiguration.Selectors
的屬性來進行判斷,那麼默認 Abp 爲咱們添加了哪些類型是一定有審計日誌的呢?
經過代碼追蹤,咱們來到了 AbpKernalModule
類的內部,在其預加載方法裏面有一個 AddAuditingSelectors()
的方法,該方法的做用就是添加了一個針對於應用服務類型的一個選擇器對象。
public sealed class AbpKernelModule : AbpModule { public override void PreInitialize() { // ... 其餘代碼 AddAuditingSelectors(); // ... 其餘代碼 } // ... 其餘代碼 private void AddAuditingSelectors() { Configuration.Auditing.Selectors.Add( new NamedTypeSelector( "Abp.ApplicationServices", type => typeof(IApplicationService).IsAssignableFrom(type) ) ); } // ... 其餘代碼 }
咱們先看一下 NamedTypeSelector
的一個做用是什麼,其基本類型定義由一個 string
和 Func<Type, bool>
組成,十分簡單,重點就出在這個斷言委託上面。
public class NamedTypeSelector { // 選擇器名稱 public string Name { get; set; } // 斷言委託 public Func<Type, bool> Predicate { get; set; } public NamedTypeSelector(string name, Func<Type, bool> predicate) { Name = name; Predicate = predicate; } }
回到最開始的地方,當 Abp 爲 Selectors 添加了一個名字爲 "Abp.ApplicationServices" 的類型選擇器。其斷言委託的大致意思就是傳入的 type 參數是繼承自 IApplicationService
接口的話,則返回 true
,不然返回 false
。
這樣在程序啓動的時候,首先注入類型的時候,會首先進入上文所述的攔截器綁定類當中,這個時候會使用 Selectors 內部的類型選擇器來調用這個集合內部的斷言委託,只要這些選擇器對象有一個返回 true
,那麼就直接與當前注入的 type 綁定攔截器。
2.代碼分析
2.1 過濾器代碼分析
首先查看這個過濾器的總體類型結構,一個標準的過濾器,確定要實現 IAsyncActionFilter
接口。從下面的代碼咱們能夠看到其注入了 IAbpAspNetCoreConfiguration
和一個 IAuditingHelper
對象。這兩個對象的做用分別是判斷是否記錄日誌,另外一個則是用來真正寫入日誌所使用的。
public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency { // 審計日誌組件配置對象 private readonly IAbpAspNetCoreConfiguration _configuration; // 真正用來寫入審計日誌的工具類 private readonly IAuditingHelper _auditingHelper; public AbpAuditActionFilter(IAbpAspNetCoreConfiguration configuration, IAuditingHelper auditingHelper) { _configuration = configuration; _auditingHelper = auditingHelper; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ... 代碼實現 } // ... 其餘代碼 }
接着看 AbpAuditActionFilter()
方法內部的實現,進入這個過濾器的時候,經過 ShouldSaveAudit()
方法來判斷是否要寫審計日誌。
以後呢與 DTO 自動驗證的過濾器同樣,經過 AbpCrossCuttingConcerns.Applying()
方法爲當前的對象增長了一個標識,用來告訴攔截器說我已經處理過了,你就不要再重複處理了。
再往下就是建立審計信息,執行具體接口方法,而且若是產生了異常的話,也會存放到審計信息當中。
最後接口不管是否執行成功,仍是說出現了異常信息,都會將其性能計數信息同審計信息一塊兒,經過 IAuditingHelper
存儲起來。
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 判斷是否寫日誌 if (!ShouldSaveAudit(context)) { await next(); return; } // 爲當前類型打上標識 using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Auditing)) { // 構造審計信息(AuditInfo) var auditInfo = _auditingHelper.CreateAuditInfo( context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, context.ActionArguments ); // 開始性能計數 var stopwatch = Stopwatch.StartNew(); try { // 嘗試調用接口方法 var result = await next(); // 產生異常以後,將其異常信息存放在審計信息之中 if (result.Exception != null && !result.ExceptionHandled) { auditInfo.Exception = result.Exception; } } catch (Exception ex) { // 產生異常以後,將其異常信息存放在審計信息之中 auditInfo.Exception = ex; throw; } finally { // 中止計數,而且存儲審計信息 stopwatch.Stop(); auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); await _auditingHelper.SaveAsync(auditInfo); } } }
2.2 攔截器代碼分析
攔截器處理時的整體思路與過濾器相似,其核心都是經過 IAuditingHelper
來建立審計信息和持久化審計信息的。只不過呢因爲攔截器不只僅是處理 MVC 接口,也會處理內部的一些類型的方法,因此針對同步方法與異步方法的處理確定會複雜一點。
攔截器呢,咱們關心一下他的核心方法 Intercept()
就好了。
public void Intercept(IInvocation invocation) { // 判斷過濾器是否已經處理了過了 if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Auditing)) { invocation.Proceed(); return; } // 經過 IAuditingHelper 來判斷當前方法是否須要記錄審計日誌信息 if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget)) { invocation.Proceed(); return; } // 構造審計信息 var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetType, invocation.MethodInvocationTarget, invocation.Arguments); // 判斷方法的類型,同步方法與異步方法的處理邏輯不同 if (invocation.Method.IsAsync()) { PerformAsyncAuditing(invocation, auditInfo); } else { PerformSyncAuditing(invocation, auditInfo); } } // 同步方法的處理邏輯與 MVC 過濾器邏輯類似 private void PerformSyncAuditing(IInvocation invocation, AuditInfo auditInfo) { var stopwatch = Stopwatch.StartNew(); try { invocation.Proceed(); } catch (Exception ex) { auditInfo.Exception = ex; throw; } finally { stopwatch.Stop(); auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); _auditingHelper.Save(auditInfo); } } // 異步方法處理 private void PerformAsyncAuditing(IInvocation invocation, AuditInfo auditInfo) { var stopwatch = Stopwatch.StartNew(); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally( (Task) invocation.ReturnValue, exception => SaveAuditInfo(auditInfo, stopwatch, exception) ); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, exception => SaveAuditInfo(auditInfo, stopwatch, exception) ); } } private void SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception exception) { stopwatch.Stop(); auditInfo.Exception = exception; auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); _auditingHelper.Save(auditInfo); }
這裏異步方法的處理在很早以前的工做單元攔截器就有過講述,這裏就再也不重複說明了。
2.3 核心的 IAuditingHelper
從代碼上咱們就能夠看到,不管是攔截器仍是過濾器都是最終都是經過 IAuditingHelper
對象來儲存審計日誌的。Abp 依舊爲咱們實現了一個默認的 AuditingHelper
,實現了其接口的全部方法。咱們先查看一下這個接口的定義:
public interface IAuditingHelper { // 判斷當前方法是否須要存儲審計日誌信息 bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false); // 根據參數集合建立一個審計信息,通常用於攔截器 AuditInfo CreateAuditInfo(Type type, MethodInfo method, object[] arguments); // 根據一個參數字典類來建立一個審計信息,通常用於 MVC 過濾器 AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary<string, object> arguments); // 同步保存審計信息 void Save(AuditInfo auditInfo); // 異步保存審計信息 Task SaveAsync(AuditInfo auditInfo); }
咱們來到其默認實現 AuditingHelper
類型,先看一下其內部注入了哪些接口。
public class AuditingHelper : IAuditingHelper, ITransientDependency { // 日誌記錄器,用於記錄日誌 public ILogger Logger { get; set; } // 用於獲取當前登陸用戶的信息 public IAbpSession AbpSession { get; set; } // 用於持久話審計日誌信息 public IAuditingStore AuditingStore { get; set; } // 主要做用是填充審計信息的客戶端調用信息 private readonly IAuditInfoProvider _auditInfoProvider; // 審計日誌組件的配置相關 private readonly IAuditingConfiguration _configuration; // 在調用 AuditingStore 進行持久化的時候使用,建立一個工做單元 private readonly IUnitOfWorkManager _unitOfWorkManager; // 用於序列化參數信息爲 JSON 字符串 private readonly IAuditSerializer _auditSerializer; public AuditingHelper( IAuditInfoProvider auditInfoProvider, IAuditingConfiguration configuration, IUnitOfWorkManager unitOfWorkManager, IAuditSerializer auditSerializer) { _auditInfoProvider = auditInfoProvider; _configuration = configuration; _unitOfWorkManager = unitOfWorkManager; _auditSerializer = auditSerializer; AbpSession = NullAbpSession.Instance; Logger = NullLogger.Instance; AuditingStore = SimpleLogAuditingStore.Instance; } // ... 其餘實現的接口 }
2.3.1 判斷是否建立審計信息
首先分析一下其內部的 ShouldSaveAudit()
方法,整個方法的核心做用就是根據傳入的方法類型來斷定是否爲其建立審計信息。
其實在這一串 if 當中,你能夠發現有一句代碼對方法是否標註了 DisableAuditingAttribute
特性進行了判斷,若是標註了該特性,則不爲該方法建立審計信息。因此咱們就能夠經過該特性來控制本身應用服務類,控制裏面的的接口是否要建立審計信息。同理,咱們也能夠經過顯式標註 AuditedAttribute
特性來讓攔截器爲這個方法建立審計信息。
public bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false) { if (!_configuration.IsEnabled) { return false; } if (!_configuration.IsEnabledForAnonymousUsers && (AbpSession?.UserId == null)) { return false; } if (methodInfo == null) { return false; } if (!methodInfo.IsPublic) { return false; } if (methodInfo.IsDefined(typeof(AuditedAttribute), true)) { return true; } if (methodInfo.IsDefined(typeof(DisableAuditingAttribute), true)) { return false; } var classType = methodInfo.DeclaringType; if (classType != null) { if (classType.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true)) { return true; } if (classType.GetTypeInfo().IsDefined(typeof(DisableAuditingAttribute), true)) { return false; } if (_configuration.Selectors.Any(selector => selector.Predicate(classType))) { return true; } } return defaultValue; }
2.3.2 建立審計信息
審計信息在建立的時候,就爲咱們將當前調用接口時的用戶信息存放在了審計信息當中,以後經過 IAuditInfoProvider
的 Fill()
方法填充了客戶端 IP 與瀏覽器信息。
public AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary<string, object> arguments) { // 構建一個審計信息對象 var auditInfo = new AuditInfo { TenantId = AbpSession.TenantId, UserId = AbpSession.UserId, ImpersonatorUserId = AbpSession.ImpersonatorUserId, ImpersonatorTenantId = AbpSession.ImpersonatorTenantId, ServiceName = type != null ? type.FullName : "", MethodName = method.Name, // 將參數轉換爲 JSON 字符串 Parameters = ConvertArgumentsToJson(arguments), ExecutionTime = Clock.Now }; try { // 填充客戶 IP 與瀏覽器信息等 _auditInfoProvider.Fill(auditInfo); } catch (Exception ex) { Logger.Warn(ex.ToString(), ex); } return auditInfo; }
2.4 審計信息持久化
經過上一小節咱們知道了在調用審計信息保存接口的時候,其實是調用的 IAuditingStore
所提供的 SaveAsync(AuditInfo auditInfo)
方法來持久化這些審計日誌信息的。
若是你沒有集成 Abp.Zero 項目的話,則使用的是默認的實現,就是簡單經過 ILogger
輸出審計信息到日誌當中。
默認有這兩種實現,至於第一種是 Abp 的單元測試項目所使用的。
這裏咱們就簡單將一下 AuditingStore
這個實現吧,其實很簡單的,就是注入了一個倉儲,在保存的時候往審計日誌表插入一條數據便可。
這裏使用了 AuditLog.CreateFromAuditInfo()
方法將 AuditInfo
類型的審計信息轉換爲數據庫實體,用於倉儲進行插入操做。
public class AuditingStore : IAuditingStore, ITransientDependency { private readonly IRepository<AuditLog, long> _auditLogRepository; public AuditingStore(IRepository<AuditLog, long> auditLogRepository) { _auditLogRepository = auditLogRepository; } public virtual Task SaveAsync(AuditInfo auditInfo) { // 向表中插入數據 return _auditLogRepository.InsertAsync(AuditLog.CreateFromAuditInfo(auditInfo)); } }
一樣,這裏建議從新實現一個 AuditingStore
,存儲在 Redis 或者其餘地方。
3. 後記
前幾天發現 Abp 的團隊有開了一個新坑,叫作 Abp vNext 框架,該框架所有基於 .NET Core 進行開發,並且會針對微服務項目進行專門的設計,有興趣的朋友能夠持續關注。
其 GitHub 地址爲:https://github.com/abpframework/abp/
官方地址爲:https://abp.io/
4.點此跳轉到總目錄
.Net 登錄的時候添加驗證碼
1、ASPX 登錄界面驗證碼
一、登錄驗證碼圖片和輸入驗證碼框
<asp:TextBox ID="txtValiCode" runat="server" Width="50px"></asp:TextBox> <asp:Image ID="ValiCode" ImageUrl="CreateValiImg.aspx" runat="server" style="position:relative;top:4px;" />
二、js
$(function () { $("#loginBtn").click(function () { var Pwd = $("#PwdTbx").val(); var md5pwd = $.md5(Pwd); $("#PwdTbx").val(md5pwd); }); $("#txtValiCode").val(""); $("#ValiCode").click(function () { location.reload(); }); });
三、建立生產驗證碼的aspx頁 CreateValiImg.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CreateValiImg.aspx.cs" Inherits="CreateValiImg" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <form id="form1" runat="server"> <div> </div> </form> </body> </html>
後臺代碼
using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class CreateValiImg : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string validateNum = CreateRandomNum(4); CreateImage(validateNum); Session["ValidateNum"] = validateNum; } //生產隨機數 private string CreateRandomNum(int NumCount) { string allChar = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,O,P,Q,R,S,T,U,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,m,n,o,p,q,s,t,u,w,x,y,z"; string[] allCharArray = allChar.Split(',');//拆分紅數組 string randomNum = ""; int temp = -1; //記錄上次隨機數的數值,儘可能避免產生幾個相同的隨機數 Random rand = new Random(); for (int i = 0; i < NumCount; i++) { if (temp != -1) { rand = new Random(i * temp * ((int)DateTime.Now.Ticks)); } int t = rand.Next(35); if (temp == t) { return CreateRandomNum(NumCount); } temp = t; randomNum += allCharArray[t]; } return randomNum; } //生產圖片 private void CreateImage(string validateNum) { if (validateNum == null || validateNum.Trim() == string.Empty) return; //生成BitMap圖像 System.Drawing.Bitmap image = new System.Drawing.Bitmap(validateNum.Length * 12 + 12, 22); Graphics g = Graphics.FromImage(image); try { //生成隨機生成器 Random random = new Random(); //清空圖片背景 g.Clear(Color.White); //畫圖片的背景噪音線 for (int i = 0; i < 25; i++) { int x1 = random.Next(image.Width); int x2 = random.Next(image.Width); int y1 = random.Next(image.Height); int y2 = random.Next(image.Height); g.DrawLine(new Pen(Color.Silver), x1, x2, y1, y2); } Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic)); System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true); g.DrawString(validateNum, font, brush, 2, 2); //畫圖片的前景噪音點 for (int i = 0; i < 100; i++) { int x = random.Next(image.Width); int y = random.Next(image.Height); image.SetPixel(x, y, Color.FromArgb(random.Next())); } //畫圖片的邊框線 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); System.IO.MemoryStream ms = new System.IO.MemoryStream(); //將圖像保存到指定流 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); Response.ClearContent(); Response.ContentType = "image/Gif"; Response.BinaryWrite(ms.ToArray()); } finally { g.Dispose(); image.Dispose(); } } }
四、運行效果
五、登錄提交帳號密碼驗證碼的時候驗證
if (ValiCode != Session["ValidateNum"].ToString()){ ... }
2、MVC中使用驗證碼
一、在登錄頁添加輸入驗證碼框和圖片
驗證碼 <input ID="txtValiCode" type="text" style="width:60px;" /> <img ID="ValiCode" src="/Login/CreatevaliImg" style="position:relative;top:4px;" />
二、登錄界面js、點擊驗證碼圖片刷新驗證碼
$(function () { $("#txtValiCode").val(""); $("#ValiCode").click(function () { location.reload(); }); });
三、在controller裏添加 CreatevaliImg 方法生產驗證碼,供界面裏img url調用
public void CreatevaliImg() { string validateNum = CreateRandomNum(4); CreateImage(validateNum); Session["ValidateNum"] = validateNum; } //生產隨機數 private string CreateRandomNum(int NumCount) { string allChar = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,O,P,Q,R,S,T,U,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,m,n,o,p,q,s,t,u,w,x,y,z"; string[] allCharArray = allChar.Split(',');//拆分紅數組 string randomNum = ""; int temp = -1; //記錄上次隨機數的數值,儘可能避免產生幾個相同的隨機數 Random rand = new Random(); for (int i = 0; i < NumCount; i++) { if (temp != -1) { rand = new Random(i * temp * ((int)DateTime.Now.Ticks)); } int t = rand.Next(35); if (temp == t) { return CreateRandomNum(NumCount); } temp = t; randomNum += allCharArray[t]; } return randomNum; } //生產圖片 private void CreateImage(string validateNum) { if (validateNum == null || validateNum.Trim() == string.Empty) return; //生成BitMap圖像 System.Drawing.Bitmap image = new System.Drawing.Bitmap(validateNum.Length * 12 + 12, 22); Graphics g = Graphics.FromImage(image); try { //生成隨機生成器 Random random = new Random(); //清空圖片背景 g.Clear(Color.White); //畫圖片的背景噪音線 for (int i = 0; i < 25; i++) { int x1 = random.Next(image.Width); int x2 = random.Next(image.Width); int y1 = random.Next(image.Height); int y2 = random.Next(image.Height); g.DrawLine(new Pen(Color.Silver), x1, x2, y1, y2); } Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic)); System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true); g.DrawString(validateNum, font, brush, 2, 2); //畫圖片的前景噪音點 for (int i = 0; i < 100; i++) { int x = random.Next(image.Width); int y = random.Next(image.Height); image.SetPixel(x, y, Color.FromArgb(random.Next())); } //畫圖片的邊框線 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); System.IO.MemoryStream ms = new System.IO.MemoryStream(); //將圖像保存到指定流 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); Response.ClearContent(); Response.ContentType = "image/Gif"; Response.BinaryWrite(ms.ToArray()); } finally { g.Dispose(); image.Dispose(); } }
四、運行效果
五、登錄的時候校驗下 頁面中的輸入內容和 後臺生成的 Session["ValidateNum"] 做校驗
使用Topshelf開發Windows服務、記錄日誌
開發windows服務,除了在vs裏新建服務項目外(以前有寫過具體開發方法,可點擊查看),還可使用Topshelf。
不過使用topshelf須要.netframework 4.5.2版本,在vs2013上引用不成功,我這裏使用的是vs2017。
如下爲具體步驟:
1、引用topshelf 並使用
一、在vs裏新建控制檯程序
二、在引用裏使用NuGet搜索topshelf並安裝
三、程序代碼
using log4net; using System; using System.IO; using System.Reflection; using System.Timers; using Topshelf; namespace TopshelfTest { public class TestWriteLog { readonly Timer _timer; ILog _log = LogManager.GetLogger(typeof(TestWriteLog)); public TestWriteLog() { _timer = new Timer(1000) { AutoReset = true, Enabled = true }; int i = 0; _timer.Elapsed += delegate (object sender, System.Timers.ElapsedEventArgs e) { i++; _log.Info("測試" + i.ToString()); }; } public void Start() { _log.Info("Service is Started"); _timer.Start(); } public void Stop() { _log.Info("Service is Stopped"); _timer.Stop(); } /// <summary> /// 應用程序的主入口點。 /// </summary> static void Main() { HostFactory.Run(c => { c.Service<TestWriteLog>((x) => { x.ConstructUsing(name => new TestWriteLog()); x.WhenStarted((t) => t.Start()); x.WhenStopped((t) => t.Stop()); }); c.RunAsLocalSystem(); //服務描述 c.SetDescription("TestServices測試服務描述"); //服務顯示名稱 c.SetDisplayName("TestServices測試服務顯示名稱"); //服務的真實名稱 c.SetServiceName("TestServices"); }); } } }
四、log4net引用和配置方法
4.1 使用以上方法引用log4net.dll
4.2 在app.config 裏做以下配置
<configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> </configSections> <log4net> <!-- OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL --> <!-- Set root logger level to ERROR and its appenders --> <root> <level value="ALL" /> <appender-ref ref="SysAppender" /> </root> <!-- Print only messages of level DEBUG or above in the packages --> <logger name="WebLogger"> <level value="log" /> </logger> <appender name="SysAppender" type="log4net.Appender.RollingFileAppender,log4net"> <!--<param name="File" value="App_Data/" />--> <File value="Logs\log" /> <!--日誌文件位置和文件名--> <param name="AppendToFile" value="true" /> <param name="RollingStyle" value="Date" /> <!--<param name="DatePattern" value=""Logs_"yyyyMMdd".txt"" />--> <param name="DatePattern" value="yyyyMMdd".txt"" /> <!--在文件名後面加內容 好比 log名變爲log20180808--> <param name="StaticLogFileName" value="false" /> <layout type="log4net.Layout.PatternLayout,log4net"> <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" /> </layout> </appender> <appender name="consoleApp" type="log4net.Appender.ConsoleAppender,log4net"> <layout type="log4net.Layout.PatternLayout,log4net"> <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" /> </layout> </appender> </log4net>
4.3 在properties的AssemblyInfo.cs裏 加以下配置
[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension = "config", Watch = true)]
五、生成項目
生成項目後能夠將bin\debug裏的內容拷出來單獨放服務的位置,
六、安裝、卸載服務
使用管理員運行cmd,cd到你的服務文件位置,我這裏直接在debug裏安裝服務的。
服務.exe install -----安裝服務
在服務裏就能夠看到安裝的服務了
服務.exe uninstall -----卸載服務
七、運行結果
在安裝服務後開啓服務
平常雜記——C#驗證碼
c#_生成圖片式驗證碼
廢話很少說直接上代碼。
1 class Check_Code 2 { 3 /// <summary> 4 /// 生成隨機驗證碼數字+字母 5 /// </summary> 6 /// <param name="codelen">驗證碼長度</param> 7 /// <returns>返回驗證碼</returns> 8 public static string MakeCode(int codelen) 9 { 10 if (codelen < 1) 11 { 12 return string.Empty; 13 } 14 int number; 15 StringBuilder strCheckCode = new StringBuilder(); 16 Random random = new Random(); 17 for (int index = 0; index < codelen; index++) 18 { 19 number = random.Next(); 20 if (number % 2 == 0) 21 { 22 strCheckCode.Append((char)('0' + (char)(number % 10)));//生成隨機數字 23 } 24 else 25 { 26 strCheckCode.Append((char)('A' + (char)(number % 26)));//生成隨機字母 27 } 28 } 29 return strCheckCode.ToString(); 30 } 31 public static MemoryStream CheckCodeImage(string CheckCode) 32 { 33 if (string.IsNullOrEmpty(CheckCode)) 34 { 35 return null; 36 } 37 Bitmap image = new Bitmap((int)Math.Ceiling((CheckCode.Length * 12.5)), 22); 38 Graphics graphic = Graphics.FromImage(image);//建立一個驗證碼圖片 39 try 40 { 41 Random random = new Random(); 42 graphic.Clear(Color.White); 43 int x1 = 0, y1 = 0, x2 = 0, y2 = 0; 44 for (int index = 0; index < 25; index++) 45 { 46 x1 = random.Next(image.Width); 47 x2 = random.Next(image.Width); 48 y1 = random.Next(image.Height); 49 y2 = random.Next(image.Height); 50 graphic.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2); 51 } 52 Font font = new Font("Arial", 12, (FontStyle.Bold | FontStyle.Italic));//Font設置字體,字號,字形 53 //設置圖形漸變色的起始顏色與終止顏色,漸變角度 54 LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Red, Color.DarkRed, 1.2f, true); 55 graphic.DrawString(CheckCode, font, brush, 2, 2); 56 int X = 0; int Y = 0; 57 //繪製圖片的前景噪點 58 for (int i = 0; i < 100; i++) 59 { 60 X = random.Next(image.Width); 61 Y = random.Next(image.Height); 62 image.SetPixel(X, Y, Color.FromArgb(random.Next())); 63 } 64 //畫圖片的邊框線 65 graphic.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); 66 //將圖片保存爲stream流返回 67 MemoryStream ms = new MemoryStream(); 68 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); 69 return ms; 70 } 71 finally 72 { 73 graphic.Dispose(); 74 image.Dispose(); 75 } 76 } 77 }
生成驗證碼圖片,須要先生成驗證碼,再將驗證碼處理爲圖片。
轉載引用黎木博客-https://www.cnblogs.com/running-mydream/p/4071528.html
本文經過一個簡單的小例子簡述SharpZipLib壓縮文件的常規用法,僅供學習分享使用,若有不足之處,還請指正。
什麼是SharpZipLib ?
SharpZipLib是一個C#的類庫,主要用來解壓縮Zip,GZip,BZip2,Tar等格式,是以託管程序集的方式實現,能夠方便的應用於其餘的項目之中。
在工程中引用SharpZipLib
在項目中,點擊項目名稱右鍵-->管理NuGet程序包,打開NuGet包管理器窗口,進行搜索下載便可,以下圖所示:
SharpZipLib的關鍵類結構圖
以下所示:
涉及知識點:
- ZipOutputStream 壓縮輸出流,將文件一個接一個的寫入壓縮文檔,此類不是線程安全的。
- PutNextEntry 開始一個新的ZIP條目,ZipOutputStream中的方法。
- ZipEntry 一個ZIP文件中的條目,能夠理解爲壓縮包裏面的一個文件夾/文件。
- ZipInputStream 解壓縮輸出流,從壓縮包中一個接一個的讀出文檔。
- GetNextEntry 讀出ZIP條目,ZipInputStream中的方法。
示例效果圖:
關於解壓縮小例子的示例效果圖,以下:
核心代碼
1 using ICSharpCode.SharpZipLib.Checksum; 2 using ICSharpCode.SharpZipLib.Zip; 3 using System; 4 using System.Collections.Generic; 5 using System.IO; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 10 namespace DemoZip 11 { 12 class ZipHelper 13 { 14 private string rootPath = string.Empty; 15 16 #region 壓縮 17 18 /// <summary> 19 /// 遞歸壓縮文件夾的內部方法 20 /// </summary> 21 /// <param name="folderToZip">要壓縮的文件夾路徑</param> 22 /// <param name="zipStream">壓縮輸出流</param> 23 /// <param name="parentFolderName">此文件夾的上級文件夾</param> 24 /// <returns></returns> 25 private bool ZipDirectory(string folderToZip, ZipOutputStream zipStream, string parentFolderName) 26 { 27 bool result = true; 28 string[] folders, files; 29 ZipEntry ent = null; 30 FileStream fs = null; 31 Crc32 crc = new Crc32(); 32 33 try 34 { 35 string entName = folderToZip.Replace(this.rootPath, string.Empty)+"/"; 36 //Path.Combine(parentFolderName, Path.GetFileName(folderToZip) + "/") 37 ent = new ZipEntry(entName); 38 zipStream.PutNextEntry(ent); 39 zipStream.Flush(); 40 41 files = Directory.GetFiles(folderToZip); 42 foreach (string file in files) 43 { 44 fs = File.OpenRead(file); 45 46 byte[] buffer = new byte[fs.Length]; 47 fs.Read(buffer, 0, buffer.Length); 48 ent = new ZipEntry(entName + Path.GetFileName(file)); 49 ent.DateTime = DateTime.Now; 50 ent.Size = fs.Length; 51 52 fs.Close(); 53 54 crc.Reset(); 55 crc.Update(buffer); 56 57 ent.Crc = crc.Value; 58 zipStream.PutNextEntry(ent); 59 zipStream.Write(buffer, 0, buffer.Length); 60 } 61 62 } 63 catch 64 { 65 result = false; 66 } 67 finally 68 { 69 if (fs != null) 70 { 71 fs.Close(); 72 fs.Dispose(); 73 } 74 if (ent != null) 75 { 76 ent = null; 77 } 78 GC.Collect(); 79 GC.Collect(1); 80 } 81 82 folders = Directory.GetDirectories(folderToZip); 83 foreach (string folder in folders) 84 if (!ZipDirectory(folder, zipStream, folderToZip)) 85 return false; 86 87 return result; 88 } 89 90 /// <summary> 91 /// 壓縮文件夾 92 /// </summary> 93 /// <param name="folderToZip">要壓縮的文件夾路徑</param> 94 /// <param name="zipedFile">壓縮文件完整路徑</param> 95 /// <param name="password">密碼</param> 96 /// <returns>是否壓縮成功</returns> 97 public bool ZipDirectory(string folderToZip, string zipedFile, string password) 98 { 99 bool result = false; 100 if (!Directory.Exists(folderToZip)) 101 return result; 102 103 ZipOutputStream zipStream = new ZipOutputStream(File.Create(zipedFile)); 104 zipStream.SetLevel(6); 105 if (!string.IsNullOrEmpty(password)) zipStream.Password = password; 106 107 result = ZipDirectory(folderToZip, zipStream, ""); 108 109 zipStream.Finish(); 110 zipStream.Close(); 111 112 return result; 113 } 114 115 /// <summary> 116 /// 壓縮文件夾 117 /// </summary> 118 /// <param name="folderToZip">要壓縮的文件夾路徑</param> 119 /// <param name="zipedFile">壓縮文件完整路徑</param> 120 /// <returns>是否壓縮成功</returns> 121 public bool ZipDirectory(string folderToZip, string zipedFile) 122 { 123 bool result = ZipDirectory(folderToZip, zipedFile, null); 124 return result; 125 } 126 127 /// <summary> 128 /// 壓縮文件 129 /// </summary> 130 /// <param name="fileToZip">要壓縮的文件全名</param> 131 /// <param name="zipedFile">壓縮後的文件名</param> 132 /// <param name="password">密碼</param> 133 /// <returns>壓縮結果</returns> 134 public bool ZipFile(string fileToZip, string zipedFile, string password) 135 { 136 bool result = true; 137 ZipOutputStream zipStream = null; 138 FileStream fs = null; 139 ZipEntry ent = null; 140 141 if (!File.Exists(fileToZip)) 142 return false; 143 144 try 145 { 146 fs = File.OpenRead(fileToZip); 147 byte[] buffer = new byte[fs.Length]; 148 fs.Read(buffer, 0, buffer.Length); 149 fs.Close(); 150 151 fs = File.Create(zipedFile); 152 zipStream = new ZipOutputStream(fs); 153 if (!string.IsNullOrEmpty(password)) zipStream.Password = password; 154 ent = new ZipEntry(Path.GetFileName(fileToZip)); 155 zipStream.PutNextEntry(ent); 156 zipStream.SetLevel(6); 157 158 zipStream.Write(buffer, 0, buffer.Length); 159 160 } 161 catch 162 { 163 result = false; 164 } 165 finally 166 { 167 if (zipStream != null) 168 { 169 zipStream.Finish(); 170 zipStream.Close(); 171 } 172 if (ent != null) 173 { 174 ent = null; 175 } 176 if (fs != null) 177 { 178 fs.Close(); 179 fs.Dispose(); 180 } 181 } 182 GC.Collect(); 183 GC.Collect(1); 184 185 return result; 186 } 187 188 /// <summary> 189 /// 壓縮文件 190 /// </summary> 191 /// <param name="fileToZip">要壓縮的文件全名</param> 192 /// <param name="zipedFile">壓縮後的文件名</param> 193 /// <returns>壓縮結果</returns> 194 public bool ZipFile(string fileToZip, string zipedFile) 195 { 196 bool result = ZipFile(fileToZip, zipedFile, null); 197 return result; 198 } 199 200 /// <summary> 201 /// 壓縮文件或文件夾 202 /// </summary> 203 /// <param name="fileToZip">要壓縮的路徑</param> 204 /// <param name="zipedFile">壓縮後的文件名</param> 205 /// <param name="password">密碼</param> 206 /// <returns>壓縮結果</returns> 207 public bool Zip(string fileToZip, string zipedFile, string password) 208 { 209 bool result = false; 210 if (Directory.Exists(fileToZip)) 211 { 212 this.rootPath = Path.GetDirectoryName(fileToZip); 213 result = ZipDirectory(fileToZip, zipedFile, password); 214 } 215 else if (File.Exists(fileToZip)) 216 { 217 this.rootPath = Path.GetDirectoryName(fileToZip); 218 result = ZipFile(fileToZip, zipedFile, password); 219 } 220 return result; 221 } 222 223 /// <summary> 224 /// 壓縮文件或文件夾 225 /// </summary> 226 /// <param name="fileToZip">要壓縮的路徑</param> 227 /// <param name="zipedFile">壓縮後的文件名</param> 228 /// <returns>壓縮結果</returns> 229 public bool Zip(string fileToZip, string zipedFile) 230 { 231 bool result = Zip(fileToZip, zipedFile, null); 232 return result; 233 234 } 235 236 #endregion 237 238 #region 解壓 239 240 /// <summary> 241 /// 解壓功能(解壓壓縮文件到指定目錄) 242 /// </summary> 243 /// <param name="fileToUnZip">待解壓的文件</param> 244 /// <param name="zipedFolder">指定解壓目標目錄</param> 245 /// <param name="password">密碼</param> 246 /// <returns>解壓結果</returns> 247 public bool UnZip(string fileToUnZip, string zipedFolder, string password) 248 { 249 bool result = true; 250 FileStream fs = null; 251 ZipInputStream zipStream = null; 252 ZipEntry ent = null; 253 string fileName; 254 255 if (!File.Exists(fileToUnZip)) 256 return false; 257 258 if (!Directory.Exists(zipedFolder)) 259 Directory.CreateDirectory(zipedFolder); 260 261 try 262 { 263 zipStream = new ZipInputStream(File.OpenRead(fileToUnZip)); 264 if (!string.IsNullOrEmpty(password)) zipStream.Password = password; 265 while ((ent = zipStream.GetNextEntry()) != null) 266 { 267 if (!string.IsNullOrEmpty(ent.Name)) 268 { 269 fileName = Path.Combine(zipedFolder, ent.Name); 270 fileName = fileName.Replace('/', '\\');//change by Mr.HopeGi 271 272 if (fileName.EndsWith("\\")) 273 { 274 Directory.CreateDirectory(fileName); 275 continue; 276 } 277 278 fs = File.Create(fileName); 279 int size = 2048; 280 byte[] data = new byte[size]; 281 while (true) 282 { 283 size = zipStream.Read(data, 0, data.Length); 284 if (size > 0) 285 fs.Write(data, 0, data.Length); 286 else 287 break; 288 } 289 } 290 } 291 } 292 catch 293 { 294 result = false; 295 } 296 finally 297 { 298 if (fs != null) 299 { 300 fs.Close(); 301 fs.Dispose(); 302 } 303 if (zipStream != null) 304 { 305 zipStream.Close(); 306 zipStream.Dispose(); 307 } 308 if (ent != null) 309 { 310 ent = null; 311 } 312 GC.Collect(); 313 GC.Collect(1); 314 } 315 return result; 316 } 317 318 /// <summary> 319 /// 解壓功能(解壓壓縮文件到指定目錄) 320 /// </summary> 321 /// <param name="fileToUnZip">待解壓的文件</param> 322 /// <param name="zipedFolder">指定解壓目標目錄</param> 323 /// <returns>解壓結果</returns> 324 public bool UnZip(string fileToUnZip, string zipedFolder) 325 { 326 bool result = UnZip(fileToUnZip, zipedFolder, null); 327 return result; 328 } 329 330 #endregion 331 } 332 }
備註
關於生成壓縮的方法還有不少,如經過命令行調用winrar的執行文件,SharpZipLib只是方法之一。
關於SharpZipLib的的API文檔,可參看連接。
關於源碼下載連接
未能將文件obj\...複製到obj\...未能找到路徑
解決辦法:將web項目文件下的obj文件夾從項目中排除,而後再發布就OK了
Sql2012如何將遠程服務器數據庫及表、表結構、表數據導入本地數據庫
一、第一步,在本地數據庫中建一個與服務器同名的數據庫
二、第二步,右鍵源數據庫,任務》導出數據,彈出導入導出提示框,點下一步繼續
三、遠程數據庫操做,確認服務器名稱(服務器地址)、身份驗證(輸入用戶名、密碼)、選擇須要導出的源數據庫,點下一步繼續
四、本地目標服務器操做,確認服務器名稱、輸入用戶名密碼、選擇要導入的目標數據庫(通常與遠程數據庫同名),點下一步繼續
五、這一步選擇默認,點下一步繼續
六、選擇須要複製的表或視圖(通常全選就是),點下一步繼續
七、選擇默認,點下一步繼續
八、選擇完成,執行表結構及表數據複製過程,表及數據複製過程
九、到這一步,表示整個過程執行成功
自定義日誌記錄功能,按日記錄,很方便
1、定義日誌類型
public enum LogType
{
Debug = 0,
Error = 1,
Info = 2,
Warn = 3
}
2、添加靜態LogHelper 類
public static class LogHelper
{
public static void Write(string msg, LogType logtype = LogType.Debug)
{
try
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
Directory.CreateDirectory(basePath);
string logPath = Path.Combine(basePath, DateTime.Today.ToString("yyyyMMdd") + ".log");
using (FileStream stream = new FileStream(logPath, FileMode.Append, FileAccess.Write, FileShare.Read))
{
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
{
string messge = string.Format("{0} {1} - 操做人:【{2}】,Messge:{3}",
DateTime.Now.ToString(), logtype.ToString(), DeluxeIdentity.Current.User.DisplayName,msg);
writer.WriteLine(messge);
writer.Flush();
}
}
}
catch (Exception e)
{
throw new Exception( "日誌寫入異常:" + e.ToString());
}
}
}
3、調用方法
LogHelper.Write("檢測擬單頁面審批意見是否重複保存異常,緣由:" + e.ToString(), LogType.Error);
4、效果
DotNetty網絡通訊框架學習