别的也很少说没直接贴代码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网络通讯框架学习